` for navigation
+- NEVER: Use uncontrolled state when component supports controlled mode
+
+**Examples**:
+
+```tsx
+// ✅ Correct
+const [value, setValue] = useState("");
+
setValue(e.target.value)} />
+
+
Navigate
+
+// ❌ Incorrect
+
// Uncontrolled
+
router.push('/page')}>Navigate
+```
diff --git a/.cursor/skills/interface-design/rules/best-practices-typography.md b/.cursor/skills/interface-design/rules/best-practices-typography.md
new file mode 100644
index 0000000..579a77c
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/best-practices-typography.md
@@ -0,0 +1,37 @@
+---
+title: Typography & Content Best Practices
+category: best-practices
+tags: typography, content, semantic-html, accessibility
+---
+
+## Typography & Content
+
+Concise rules for typography and content with the Mini Apps UI Kit. Use MUST/SHOULD/NEVER to guide decisions.
+
+- MUST: Use `Typography` component instead of raw HTML text elements (`
`, ` `, ``, etc.)
+- MUST: Use semantic HTML via `as` prop (e.g., `as="h1"` for headings, `as="p"` for paragraphs)
+- MUST: Use `h1` only once per page for the main document title
+- MUST: Maintain proper heading hierarchy (h1 → h2 → h3, don't skip levels)
+- SHOULD: Use appropriate variant for content type (display for heroes, heading for titles, body for paragraphs)
+- NEVER: Use raw HTML text elements when Typography component is available
+- NEVER: Skip heading levels in document structure
+
+**Examples**:
+
+```tsx
+// ✅ Correct
+
+ Main Page Title
+
+
+ Section Heading
+
+
+ Paragraph text
+
+
+// ❌ Incorrect
+Main Page Title
+Section Heading // Skipped h2
+Paragraph text
+```
diff --git a/.cursor/skills/interface-design/rules/best-practices-validation.md b/.cursor/skills/interface-design/rules/best-practices-validation.md
new file mode 100644
index 0000000..88efd2b
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/best-practices-validation.md
@@ -0,0 +1,34 @@
+---
+title: Error Handling & Validation Best Practices
+category: best-practices
+tags: validation, error-handling, forms, feedback
+---
+
+## Error Handling & Validation
+
+Concise rules for error handling and validation with the Mini Apps UI Kit. Use MUST/SHOULD/NEVER to guide decisions.
+
+- MUST: Use `Form.Field` with `has-error` className for error styling
+- MUST: Show errors inline using `Form.Message` with `error` prop
+- MUST: Focus first error field on form submission
+- MUST: Allow form submission to surface validation errors
+- SHOULD: Use `textAlign="center"` for OTPField error messages
+- NEVER: Hide validation errors or prevent form submission to show errors
+- NEVER: Use error styling without `Form.Field` wrapper
+
+**Examples**:
+
+```tsx
+// ✅ Correct
+
+
+
+
+
+ Please enter a valid email
+
+
+
+// ❌ Incorrect
+ // Missing Form wrapper
+```
diff --git a/.cursor/skills/interface-design/rules/bottombar.md b/.cursor/skills/interface-design/rules/bottombar.md
new file mode 100644
index 0000000..b832e89
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/bottombar.md
@@ -0,0 +1,48 @@
+---
+title: BottomBar Component
+category: layout-navigation
+tags: bottombar, actions, buttons, mobile
+---
+
+## BottomBar
+
+A container for action buttons at the bottom of a view. Provides a consistent layout for primary and secondary actions, supporting both horizontal and vertical arrangements.
+
+**Key Props**:
+
+- `direction` ("horizontal" | "vertical"): Button arrangement (default: "horizontal")
+- `children` (ReactNode): Button elements
+
+**Examples**:
+
+```tsx
+// Horizontal layout (default)
+
+ Cancel
+ Confirm
+
+
+// Vertical layout
+
+ Cancel
+ Confirm
+
+
+// With LiveFeedback
+
+
+
+ Cancel
+
+ Submit
+
+
+```
+
+**Edge Cases**:
+
+- Buttons should use `fullWidth` for consistent sizing
+- Vertical layout useful for narrow viewports or long button labels
+- Commonly used with LiveFeedback for async operations
+
+**Storybook Reference**: `stories/BottomBar.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/bulletlist.md b/.cursor/skills/interface-design/rules/bulletlist.md
new file mode 100644
index 0000000..3b3cafa
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/bulletlist.md
@@ -0,0 +1,88 @@
+---
+title: BulletList Component
+category: utilities
+tags: bulletlist, list, icons, circular-icon
+---
+
+## BulletList
+
+A component that renders a list of BulletListItems with custom bullet points (typically icons). Use for feature lists, step-by-step instructions, or any ordered list with custom bullets.
+
+**Key Components**:
+
+- `BulletList`: Container component
+- `BulletListItem`: Individual list item
+
+**Key Props**:
+
+- `BulletListItem`:
+ - `bulletPoint` (ReactNode): Custom bullet element (typically CircularIcon with icon)
+
+**Examples**:
+
+```tsx
+// Basic bullet list
+
+
+
+
+ }
+ >
+ First bullet point with spark icon
+
+
+
+
+ }
+ >
+ Second bullet point
+
+
+
+// With custom bullets
+
+
+
+
+ }
+ >
+ Success item
+
+
+
+
+ }
+ >
+ Error item
+
+
+
+// With Typography
+
+
+
+
+ }
+ >
+ Regular text item with body styling
+
+
+```
+
+**Edge Cases**:
+
+- Bullet points are typically CircularIcon components with icons
+- Supports custom content in list items (text, Typography, custom elements)
+- Long text automatically wraps
+
+**Storybook Reference**: `stories/BulletList.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/button.md b/.cursor/skills/interface-design/rules/button.md
new file mode 100644
index 0000000..ba86604
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/button.md
@@ -0,0 +1,53 @@
+---
+title: Button Component
+category: buttons-actions
+tags: button, action, icon, variants
+---
+
+## Button
+
+A versatile button component that supports different variants, sizes, and states with optional icon support. Use for primary actions, secondary actions, or icon-only buttons.
+
+**Key Props**:
+
+- `variant` ("primary" | "secondary" | "tertiary"): Button style variant
+- `size` ("sm" | "lg" | "icon"): Button size
+- `fullWidth` (boolean): Makes button full width
+- `disabled` (boolean): Disables the button
+- `children` (ReactNode): Button content (text, icon, or both)
+- `onClick` (function): Click handler
+
+**Examples**:
+
+```tsx
+// Primary button
+Click Me
+
+// Secondary button
+Cancel
+
+// Icon button
+
+
+
+
+// Button with icon and text
+
+
+ Button
+
+
+// Full width
+Submit
+
+// Disabled
+Disabled
+```
+
+**Edge Cases**:
+
+- Icon-only buttons should use `size="icon"`
+- Icons and text can be combined in children
+- Full width buttons are useful in forms and bottom bars
+
+**Storybook Reference**: `stories/Button.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/chip.md b/.cursor/skills/interface-design/rules/chip.md
new file mode 100644
index 0000000..6caa31d
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/chip.md
@@ -0,0 +1,54 @@
+---
+title: Chip Component
+category: buttons-actions
+tags: chip, label, status, variant
+---
+
+## Chip
+
+A chip component that displays labels with optional icons and different variants including success, warning, error, and important states. Use for status indicators, tags, or labels.
+
+**Key Props**:
+
+- `label` (string): Chip text content
+- `variant` ("default" | "success" | "warning" | "error" | "important"): Color variant
+- `icon` (ReactNode): Optional icon to display
+- `className` (string): Additional CSS classes
+
+**Examples**:
+
+```tsx
+// Default chip
+
+
+// With icon
+ } />
+
+// Success variant
+ } />
+
+// Warning variant
+ } />
+
+// Error variant
+ } />
+
+// Important variant
+ } />
+
+// Custom colors
+ }
+/>
+```
+
+**Edge Cases**:
+
+- Icons should match variant colors for consistency
+- Custom colors can override default variant colors via className
+- Variants apply background and text color classes
+
+**Storybook Reference**: `stories/Chip.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/circularicon.md b/.cursor/skills/interface-design/rules/circularicon.md
new file mode 100644
index 0000000..230099f
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/circularicon.md
@@ -0,0 +1,42 @@
+---
+title: CircularIcon Component
+category: utilities
+tags: circular-icon, icon, container, bullet
+---
+
+## CircularIcon
+
+A circular icon container component. Use for displaying icons in circular backgrounds, often used as bullet points or status indicators.
+
+**Key Props**:
+
+- `children` (ReactNode): Icon element
+- `size` ("xs" | "sm" | "md" | "lg" | "xl"): Size variant
+- `className` (string): Additional CSS classes (use for background colors, custom sizing)
+
+**Examples**:
+
+```tsx
+// Basic circular icon
+
+
+
+
+// Custom size and color
+
+
+
+
+// With different background
+
+
+
+```
+
+**Edge Cases**:
+
+- Size prop provides standard sizes, className can override
+- Background colors controlled via className
+- Icon color should contrast with background
+
+**Storybook Reference**: `stories/CircularIcon.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/circularstate.md b/.cursor/skills/interface-design/rules/circularstate.md
new file mode 100644
index 0000000..fe74152
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/circularstate.md
@@ -0,0 +1,41 @@
+---
+title: CircularState Component
+category: utilities
+tags: circular-state, status, indicator, state
+---
+
+## CircularState
+
+A circular state indicator component that displays different states (success, error, warning, pending, critical) with appropriate colors and icons.
+
+**Key Props**:
+
+- `value` ("success" | "error" | "warning" | "pending" | "critical"): State value
+- `size` ("xs" | "sm" | "md" | "lg" | "xl"): Size variant
+
+**Examples**:
+
+```tsx
+// Success state
+
+
+// Error state
+
+
+// Warning state
+
+
+// Pending state
+
+
+// Critical state
+
+```
+
+**Edge Cases**:
+
+- Each state has predefined colors and icons
+- Size controls the overall component size
+- Use for status indicators, loading states, or validation feedback
+
+**Storybook Reference**: `stories/CircularState.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/colorpicker.md b/.cursor/skills/interface-design/rules/colorpicker.md
new file mode 100644
index 0000000..4697a26
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/colorpicker.md
@@ -0,0 +1,43 @@
+---
+title: ColorPicker Component
+category: interactive-elements
+tags: colorpicker, color, selection, input
+---
+
+## ColorPicker
+
+A color picker input component with a visual color preview. Allows users to select from a predefined set of colors.
+
+**Key Components**:
+
+- `ColorPickerGroup`: Container for color items
+- `ColorPickerItem`: Individual color option
+
+**Key Props**:
+
+- `ColorPickerGroup`:
+ - `value` (string): Selected color value
+ - `onChange` (function): Callback when color changes
+ - `disabled` (boolean): Disables the picker
+- `ColorPickerItem`:
+ - `value` (string): Color hex value (e.g., "#FF0000")
+
+**Examples**:
+
+```tsx
+// Basic color picker
+
+
+
+
+
+
+```
+
+**Edge Cases**:
+
+- Colors should be provided as hex values
+- Group manages selection state
+- Disabled state prevents selection
+
+**Storybook Reference**: `stories/ColorPicker.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/countrydrawer.md b/.cursor/skills/interface-design/rules/countrydrawer.md
new file mode 100644
index 0000000..846d0c0
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/countrydrawer.md
@@ -0,0 +1,65 @@
+---
+title: CountryDrawer Component
+category: overlays-dialogs
+tags: country, drawer, selection, phone, rtl
+---
+
+## CountryDrawer
+
+A responsive country selection component that adapts to different screen sizes. On mobile devices, it displays as a full-page drawer interface, while on desktop it renders as a centered dialog modal. Typically used in conjunction with phone number input fields.
+
+**Key Props**:
+
+- `value` (CountryCode): Selected country code
+- `onChange` (function): Callback when country changes
+- `countries` (CountryCode[]): Restricted list of available countries (optional)
+- `disabled` (boolean): Disables the drawer
+- `dir` ("ltr" | "rtl"): Text direction
+- `title` (string): Custom title
+- `searchLabel` (string): Custom search placeholder
+
+**Examples**:
+
+```tsx
+// Basic country drawer
+const [country, setCountry] = useState("US");
+
+ setCountry(code)}>
+
+
+
+
+
+// With restricted countries
+ setCountry(code)}
+>
+
+
+
+
+
+// RTL support
+ setCountry(code)}
+ dir="rtl"
+ title="البلد"
+ searchLabel="البحث عن البلد"
+>
+
+
+
+
+```
+
+**Edge Cases**:
+
+- Responsive: drawer on mobile, dialog on desktop
+- Includes search functionality for quick country lookup
+- Countries are alphabetically grouped
+- RTL support for Arabic and other RTL languages
+
+**Storybook Reference**: `stories/CountryDrawer.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/drawer.md b/.cursor/skills/interface-design/rules/drawer.md
new file mode 100644
index 0000000..010e864
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/drawer.md
@@ -0,0 +1,82 @@
+---
+title: Drawer Component
+category: overlays-dialogs
+tags: drawer, modal, bottom-sheet, vaul, mobile
+---
+
+## Drawer
+
+A drawer component that slides up from the bottom of the screen. Built on top of Vaul and Radix UI Dialog. Use for mobile-friendly modals, bottom sheets, or slide-up content.
+
+**Key Components**:
+
+- `Drawer`: Root component
+- `DrawerTrigger`: Button/element that opens the drawer
+- `DrawerContent`: Main content container
+- `DrawerHeader`: Header section
+- `DrawerTitle`: Title text
+- `DrawerClose`: Element that closes drawer on click
+- `DrawerHeader`: Optional header with icon support
+
+**Key Props**:
+
+- `height` ("default" | "full"): Drawer height
+- `repositionInputs` (boolean): Repositions inputs when keyboard appears (mobile)
+
+**Examples**:
+
+```tsx
+// Basic drawer
+
+
+ Open
+
+
+
+ Drawer title
+
+ Drawer content
+
+ Close
+
+
+
+
+// Full page drawer
+
+
+ Open Full Drawer
+
+
+
+ Full Page Drawer
+
+
+ {/* Scrollable content */}
+
+
+ Close
+
+
+
+
+// With inputs (mobile keyboard handling)
+
+
+ Open
+
+
+
+ Submit
+
+
+```
+
+**Edge Cases**:
+
+- Use `asChild` on DrawerTrigger to use custom trigger elements
+- `repositionInputs` helps with mobile keyboard issues
+- Full height drawers need scrollable content areas
+- Any element wrapped in `DrawerClose` will close the drawer on click
+
+**Storybook Reference**: `stories/Drawer.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/flag.md b/.cursor/skills/interface-design/rules/flag.md
new file mode 100644
index 0000000..1a46c22
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/flag.md
@@ -0,0 +1,38 @@
+---
+title: Flag Component
+category: feedback-display
+tags: flag, country, region, svg
+---
+
+## Flag
+
+A component that displays a country or region flag using SVG icons. Use for country selection, user profiles, or location indicators.
+
+**Key Props**:
+
+- `countryCode` (CountryCode): ISO 3166-1 alpha-2 country code (e.g., "US", "GB", "FR", "DE")
+- `size` (number): Width and height in pixels (default: 40px)
+
+**Examples**:
+
+```tsx
+// Basic flag
+
+
+// Custom size
+
+
+// In country selector
+
+
+ {countryName}
+
+```
+
+**Edge Cases**:
+
+- Country codes must be valid ISO 3166-1 alpha-2 codes
+- SVG flags are included in the package
+- Size defaults to 40px if not specified
+
+**Storybook Reference**: `stories/Flag.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-checkbox.md b/.cursor/skills/interface-design/rules/form-checkbox.md
new file mode 100644
index 0000000..ab3a7e1
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-checkbox.md
@@ -0,0 +1,35 @@
+---
+title: Checkbox Component
+category: form-controls
+tags: checkbox, form, radix-ui, accessibility
+---
+
+## Checkbox
+
+A checkbox component that allows users to select one or multiple options. Built on Radix UI Checkbox primitive for accessibility.
+
+**Key Props**:
+
+- `checked` (boolean): Checked state
+- `onChange` (function): Callback when checked state changes
+- `disabled` (boolean): Disables the checkbox
+- `id` (string): HTML id attribute
+
+**Examples**:
+
+```tsx
+// Controlled checkbox
+const [checked, setChecked] = useState(false);
+
+
+// Disabled
+
+```
+
+**Edge Cases**:
+
+- Always use controlled state for React
+- Disabled state prevents all interaction
+- Includes proper ARIA attributes via Radix UI
+
+**Storybook Reference**: `stories/Checkbox.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-input.md b/.cursor/skills/interface-design/rules/form-input.md
new file mode 100644
index 0000000..0620403
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-input.md
@@ -0,0 +1,58 @@
+---
+title: Input Component
+category: form-controls
+tags: input, form, validation, adornments
+---
+
+## Input
+
+A customizable text input component that supports adornments and various states. Use for single-line text input with optional icons or validation states.
+
+**Key Props**:
+
+- `label` (string): Label text for the input
+- `id` (string): HTML id attribute
+- `variant` ("default" | "floating-label"): Label display style
+- `startAdornment` (ReactNode): Icon or element at the start
+- `endAdornment` (ReactNode): Icon or element at the end
+- `disabled` (boolean): Disables user interaction
+- `error` (boolean): Shows error styling
+- `value`, `onChange`: Standard controlled input props
+
+**Examples**:
+
+```tsx
+// Basic input
+
+
+// With floating label
+
+
+// With icons
+ }
+ endAdornment={ }
+/>
+
+// Disabled state
+
+
+// Error state with Form integration
+
+
+
+
+
+ Please enter a valid email
+
+
+```
+
+**Edge Cases**:
+
+- Long labels automatically wrap with floating-label variant
+- Error state requires Form.Field with `has-error` className for proper styling
+- Icons should use consistent sizing (typically 20x20px)
+
+**Storybook Reference**: `stories/Input.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-otpfield.md b/.cursor/skills/interface-design/rules/form-otpfield.md
new file mode 100644
index 0000000..e022e43
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-otpfield.md
@@ -0,0 +1,43 @@
+---
+title: OTPField Component
+category: form-controls
+tags: otp, code, input, validation
+---
+
+## OTPField
+
+A one-time password input field that allows entering numeric codes. Displays multiple input slots for code entry.
+
+**Key Props**:
+
+- `maxLength` (number): Number of input slots (default: 6)
+- `value` (string): Current code value
+- `onChange` (function): Callback when value changes
+- `onComplete` (function): Callback when all slots are filled
+- `error` (boolean): Error state styling
+- `mode` ("digits" | "chars" | "digitsAndChars"): Input mode
+
+**Examples**:
+
+```tsx
+// Basic OTP field
+
+
+// With error state
+
+
+
+
+ Error message
+
+
+
+```
+
+**Edge Cases**:
+
+- Automatically moves focus to next slot on input
+- Error state applies to all slots
+- Supports paste functionality
+
+**Storybook Reference**: `stories/OTPField.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-passwordfield.md b/.cursor/skills/interface-design/rules/form-passwordfield.md
new file mode 100644
index 0000000..cfcdae5
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-passwordfield.md
@@ -0,0 +1,29 @@
+---
+title: PasswordField Component
+category: form-controls
+tags: password, input, security, visibility
+---
+
+## PasswordField
+
+A password input component with a toggle to show/hide the password. Provides secure password entry with visibility control.
+
+**Key Props**:
+
+- `label` (string): Input label
+- `value`, `onChange`: Standard controlled input props
+- `disabled` (boolean): Disables the input
+- `error` (boolean): Error state styling
+
+**Examples**:
+
+```tsx
+
+```
+
+**Edge Cases**:
+
+- Password visibility toggle is built-in
+- Follows standard Input component patterns for styling
+
+**Storybook Reference**: `stories/PasswordField.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-phonefield.md b/.cursor/skills/interface-design/rules/form-phonefield.md
new file mode 100644
index 0000000..71f735d
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-phonefield.md
@@ -0,0 +1,61 @@
+---
+title: PhoneField Component
+category: form-controls
+tags: phone, input, country, formatting, rtl
+---
+
+## PhoneField
+
+A phone number input field with country code selection and formatting support. Includes a drawer for country selection and automatic phone number formatting.
+
+**Key Props**:
+
+- `value` (string): Phone number value
+- `onChange` (function): Callback when value changes
+- `locale` (string): Locale for formatting (e.g., "en", "de")
+- `dir` ("ltr" | "rtl"): Text direction
+- `defaultCountryCode` (CountryCode): Initial country code
+- `countries` (CountryCode[]): Restricted list of available countries
+- `disableDialCodePrefill` (boolean): Prevents auto-filling dial code
+- `error` (boolean): Error state styling
+- `isValid` (boolean): Success state styling
+- `label` (string): Input label
+
+**Examples**:
+
+```tsx
+// Basic phone field
+
+
+// With restricted countries
+
+
+// With validation
+const isValid = value.length >= 12;
+
+
+// RTL support
+
+```
+
+**Edge Cases**:
+
+- Phone numbers are automatically formatted based on selected country
+- Country drawer opens on mobile as full-page, desktop as dialog
+- RTL support available for Arabic and other RTL languages
+- Validation state can be controlled via `isValid` prop
+
+**Storybook Reference**: `stories/PhoneField.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-radiogroup.md b/.cursor/skills/interface-design/rules/form-radiogroup.md
new file mode 100644
index 0000000..9b58ceb
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-radiogroup.md
@@ -0,0 +1,51 @@
+---
+title: RadioGroup Component
+category: form-controls
+tags: radio, radiogroup, form, radix-ui
+---
+
+## RadioGroup
+
+A radio group component that extends Radix's RadioGroup primitive, allowing users to select a single option from a list of options.
+
+**Key Props**:
+
+- `defaultValue` (string): Uncontrolled default value
+- `value`, `onValueChange`: Controlled value
+- `orientation` ("vertical" | "horizontal"): Layout direction (default: "vertical")
+- `name` (string): HTML name attribute for form submission
+
+**Subcomponents**:
+
+- `RadioGroupItem`: Individual radio button
+
+**Examples**:
+
+```tsx
+// Basic radio group
+
+
+
+
+
+
+// With labels
+
+
+
+ Option 1
+
+
+
+ Option 2
+
+
+```
+
+**Edge Cases**:
+
+- Can be controlled or uncontrolled
+- Supports both vertical and horizontal orientations
+- Each RadioGroupItem should have a unique id when using labels
+
+**Storybook Reference**: `stories/RadioGroup.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-searchfield.md b/.cursor/skills/interface-design/rules/form-searchfield.md
new file mode 100644
index 0000000..25014a2
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-searchfield.md
@@ -0,0 +1,45 @@
+---
+title: SearchField Component
+category: form-controls
+tags: search, input, paste, validation
+---
+
+## SearchField
+
+A text input component designed for search functionality. Includes optional paste button and validation states.
+
+**Key Props**:
+
+- `label` (string): Search placeholder/label
+- `value`, `onChange`: Standard controlled input props
+- `showPasteButton` (boolean): Shows paste button as end adornment
+- `pasteButtonLabel` (string): Label for paste button
+- `error` (boolean): Error state styling
+- `isValid` (boolean): Success state styling
+- `disabled` (boolean): Disables the input
+- `endAdornment` (ReactNode): Custom end adornment
+
+**Examples**:
+
+```tsx
+// Basic search
+
+
+// With paste button
+
+
+// With error state
+
+
+
+ This is an error message
+
+
+```
+
+**Edge Cases**:
+
+- Paste button appears as end adornment when `showPasteButton` is true
+- Error state integrates with Form component
+
+**Storybook Reference**: `stories/SearchField.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-select.md b/.cursor/skills/interface-design/rules/form-select.md
new file mode 100644
index 0000000..156abf8
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-select.md
@@ -0,0 +1,74 @@
+---
+title: Select Component
+category: form-controls
+tags: select, dropdown, radix-ui, form
+---
+
+## Select
+
+A select component that provides a dropdown list of options with support for default values, disabled state, and error handling. Built on Radix UI Select primitive.
+
+**Key Props**:
+
+- `options` (SelectOption[]): Array of {value, label} objects
+- `value` (string | undefined): Controlled value
+- `onChange` (function): Callback when selection changes
+- `defaultValue` (string): Uncontrolled default value
+- `placeholder` (string): Placeholder text
+- `disabled` (boolean): Disables the select
+- `error` (boolean): Error state styling
+- `defaultOpen` (boolean): Opens dropdown on mount
+- `open`, `onOpenChange`: Controlled open state
+
+**Examples**:
+
+```tsx
+const options = [
+ { value: "10", label: "$10" },
+ { value: "25", label: "$25" },
+ { value: "50", label: "$50" },
+];
+
+// Basic select
+
+
+// With default value
+
+
+// With error state
+
+
+
+
+
+ Error message
+
+
+
+// Controlled open state
+
+```
+
+**Edge Cases**:
+
+- Supports long lists with scrolling
+- Can be controlled or uncontrolled
+- Error state requires Form.Field with `has-error` className
+- Dropdown viewport is rendered in document.body
+
+**Storybook Reference**: `stories/Select.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-switch.md b/.cursor/skills/interface-design/rules/form-switch.md
new file mode 100644
index 0000000..a659ae1
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-switch.md
@@ -0,0 +1,34 @@
+---
+title: Switch Component
+category: form-controls
+tags: switch, toggle, form, accessibility
+---
+
+## Switch
+
+A toggle switch component that provides a visual way to turn an option on or off. Built with accessibility in mind.
+
+**Key Props**:
+
+- `checked` (boolean): Checked state
+- `onChange` (function): Callback when toggled
+- `disabled` (boolean): Disables the switch
+
+**Examples**:
+
+```tsx
+// Controlled switch
+const [checked, setChecked] = useState(false);
+
+
+// Disabled
+
+```
+
+**Edge Cases**:
+
+- Always use controlled state
+- Disabled state prevents interaction
+- Includes proper ARIA attributes (role="switch", aria-checked)
+
+**Storybook Reference**: `stories/Switch.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-textarea.md b/.cursor/skills/interface-design/rules/form-textarea.md
new file mode 100644
index 0000000..47853a7
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-textarea.md
@@ -0,0 +1,46 @@
+---
+title: TextArea Component
+category: form-controls
+tags: textarea, multiline, input, floating-label
+---
+
+## TextArea
+
+A multi-line text input component that allows users to enter longer form content. Supports floating labels and various states.
+
+**Key Props**:
+
+- `label` (string): Textarea label
+- `id` (string): HTML id attribute
+- `variant` ("default" | "floating-label"): Label display style
+- `value`, `onChange`: Standard controlled input props
+- `disabled` (boolean): Disables the textarea
+- `error` (boolean): Error state styling
+- `isFocused` (boolean): Manually control focus state
+
+**Examples**:
+
+```tsx
+// Basic textarea
+
+
+// Floating label
+
+
+// With error
+
+
+// Disabled
+
+```
+
+**Edge Cases**:
+
+- Supports both default and floating-label variants
+- Can be controlled with `isFocused` prop
+
+**Storybook Reference**: `stories/TextArea.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-utilities.md b/.cursor/skills/interface-design/rules/form-utilities.md
new file mode 100644
index 0000000..21e53c5
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-utilities.md
@@ -0,0 +1,78 @@
+---
+title: Form Utilities
+category: utilities
+tags: form, validation, radix-ui, error-handling
+---
+
+## Form
+
+A form component that provides form validation and submission handling functionality. Built on Radix UI Form primitives. Use for form validation, error display, and form state management.
+
+**Key Components**:
+
+- `Form.Root`: Root form component
+- `Form.Field`: Field container with validation
+- `Form.Control`: Control wrapper (use `asChild` for custom inputs)
+- `Form.Message`: Validation/error message display
+
+**Key Props**:
+
+- `Form.Field`:
+ - `name` (string): Field name
+ - `className` (string): Use `"has-error"` for error styling
+- `Form.Message`:
+ - `error` (boolean): Error message styling
+ - `textAlign` ("left" | "center" | "right"): Text alignment
+
+**Examples**:
+
+```tsx
+// Basic form with validation
+
+
+
+
+
+ This field is required
+
+
+
+// With error state
+
+
+
+
+
+ Please enter a valid email
+
+
+
+// With Select
+
+
+
+
+
+ Error message
+
+
+
+// With OTPField
+
+
+
+
+ Error message
+
+
+
+```
+
+**Edge Cases**:
+
+- `has-error` className on Form.Field is required for error styling
+- Form.Control uses `asChild` to wrap custom input components
+- Form.Message supports different text alignments
+- Works with Input, Select, PhoneField, SearchField, OTPField, etc.
+
+**Storybook Reference**: `stories/Form.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/form-walletaddressfield.md b/.cursor/skills/interface-design/rules/form-walletaddressfield.md
new file mode 100644
index 0000000..8ce5b66
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/form-walletaddressfield.md
@@ -0,0 +1,44 @@
+---
+title: WalletAddressField Component
+category: form-controls
+tags: wallet, address, input, validation
+---
+
+## WalletAddressField
+
+A text input component designed for entering wallet addresses. Includes validation states for valid/invalid addresses.
+
+**Key Props**:
+
+- `label` (string): Input label
+- `value`, `onChange`: Standard controlled input props
+- `error` (boolean): Error state styling
+- `isValid` (boolean): Success state styling
+- `disabled` (boolean): Disables the input
+
+**Examples**:
+
+```tsx
+// Basic wallet address field
+;
+
+// With validation
+const [value, setValue] = useState('')
+const isEthAddress = /^0x[a-fA-F0-9]{40}$/.test(value)
+
+; setValue(e.target.value)}
+ isValid={isEthAddress}
+ error={value.length > 0 && !isEthAddress}
+ label="Enter ETH wallet address"
+/>
+```
+
+**Edge Cases**:
+
+- Validation should be handled by parent component
+- `isValid` shows success state (green checkmark)
+- `error` shows error state (red border)
+
+**Storybook Reference**: `stories/WalletAddressField.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/haptic.md b/.cursor/skills/interface-design/rules/haptic.md
new file mode 100644
index 0000000..af6f0bd
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/haptic.md
@@ -0,0 +1,43 @@
+---
+title: Haptic Component
+category: utilities
+tags: haptic, feedback, mobile, touch
+---
+
+## Haptic
+
+A component that provides haptic feedback when touched. Supports different types of haptic feedback including impact, notification, and selection changed. Note: All interactive components in the UI Kit already include appropriate haptic feedback by default. This component is for custom implementations.
+
+**Key Props**:
+
+- `variant` ("impact" | "notification" | "selection"): Haptic feedback type
+- `type` ("light" | "medium" | "heavy" | "success" | "warning" | "error"): Feedback intensity/type
+- `children` (ReactNode): Element to wrap
+- `asChild` (boolean): Render as child component
+
+**Examples**:
+
+```tsx
+// Basic haptic wrapper
+
+ Touch me for haptic feedback
+
+
+// Impact feedback
+
+ Medium impact
+
+
+// Notification feedback
+
+ Success notification
+
+```
+
+**Edge Cases**:
+
+- Only works on mobile devices with haptic support
+- Most UI Kit components already include haptics
+- Use for custom components that need haptic feedback
+
+**Storybook Reference**: `stories/Haptic.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/i18n.md b/.cursor/skills/interface-design/rules/i18n.md
new file mode 100644
index 0000000..8e4277c
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/i18n.md
@@ -0,0 +1,48 @@
+---
+title: Internationalization (i18n)
+category: utilities
+tags: i18n, internationalization, locale, translation, rtl
+---
+
+## Internationalization (i18n)
+
+The UI Kit includes internationalization support through the `I18nProvider` and related utilities.
+
+**Key Exports**:
+
+- `I18nProvider`: Context provider for i18n
+- `useI18n`: Hook for accessing translations
+- `loadLocale`: SSR-friendly locale loader
+- `availableLocales`: List of available locales
+
+**Examples**:
+
+```tsx
+// Server-side (Next.js)
+import { I18nProvider, loadLocale } from '@worldcoin/mini-apps-ui-kit-react'
+// Client-side
+import { useI18n } from '@worldcoin/mini-apps-ui-kit-react'
+
+export default async function Page({ searchParams }) {
+ const { locale } = await searchParams
+ const localeValue = typeof locale === 'string' ? locale.toLowerCase() : 'en'
+ const localeData = await loadLocale(localeValue)
+
+ return (
+
+
+
+ )
+}
+
+function MyComponent() {
+ const { t } = useI18n()
+ return {t('key')}
+}
+```
+
+**Edge Cases**:
+
+- Use `loadLocale` for SSR compatibility
+- Locale data must be provided to I18nProvider
+- Components like PhoneField use i18n for formatting
diff --git a/.cursor/skills/interface-design/rules/listitem.md b/.cursor/skills/interface-design/rules/listitem.md
new file mode 100644
index 0000000..e1ba534
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/listitem.md
@@ -0,0 +1,58 @@
+---
+title: ListItem Component
+category: interactive-elements
+tags: listitem, list, display, variant
+---
+
+## ListItem
+
+A versatile list item component that can be styled with different variants: duotone (alternating backgrounds), outline (bordered), and ghost (minimal). Used for displaying content in list format with consistent height and alignment.
+
+**Key Props**:
+
+- `label` (string): Primary text content
+- `description` (string): Secondary text content
+- `startAdornment` (ReactNode): Element at the start (icon, token, etc.)
+- `endAdornment` (ReactNode): Element at the end (icon, chip, etc.)
+- `disabled` (boolean): Disables the item
+- `variant` ("duotone" | "outline" | "ghost"): Styling variant
+
+**Examples**:
+
+```tsx
+// Basic list item
+
+
+// With start adornment
+ }
+/>
+
+// With end adornment
+ }
+/>
+
+// With both adornments
+ }
+ endAdornment={ }
+/>
+```
+
+**Edge Cases**:
+
+- Adornments can be any ReactNode (icons, tokens, chips, etc.)
+- Variants provide different visual styles
+- Disabled state prevents interaction
+
+**Storybook Reference**: `stories/ListItem.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/livefeedback.md b/.cursor/skills/interface-design/rules/livefeedback.md
new file mode 100644
index 0000000..0efbfa7
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/livefeedback.md
@@ -0,0 +1,52 @@
+---
+title: LiveFeedback Component
+category: feedback-display
+tags: feedback, async, loading, state
+---
+
+## LiveFeedback
+
+A wrapper component that provides visual feedback (pending, success, failed) for async operations. Wraps buttons or other interactive elements to show loading and result states.
+
+**Key Props**:
+
+- `state` ("pending" | "success" | "failed" | undefined): Current feedback state
+- `label` (object): Labels for each state `{ pending: string, success: string, failed: string }`
+- `children` (ReactNode): Element to wrap (typically a Button)
+
+**Examples**:
+
+```tsx
+const [state, setState] = useState<"pending" | "success" | "failed" | undefined>();
+
+const handleClick = () => {
+ setState("pending");
+ // Simulate API call
+ setTimeout(() => {
+ setState(Math.random() > 0.5 ? "success" : "failed");
+ setTimeout(() => setState(undefined), 2000);
+ }, 1500);
+};
+
+
+ Submit
+
+
+// With BottomBar
+
+
+
+ Cancel
+
+ Submit
+
+
+```
+
+**Edge Cases**:
+
+- State should be reset to `undefined` after showing result
+- Works with any child component, not just buttons
+- Labels are optional, defaults provided
+
+**Storybook Reference**: `stories/LiveFeedback.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/marble.md b/.cursor/skills/interface-design/rules/marble.md
new file mode 100644
index 0000000..8a6332d
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/marble.md
@@ -0,0 +1,41 @@
+---
+title: Marble Component
+category: feedback-display
+tags: marble, avatar, image, circular
+---
+
+## Marble
+
+A circular component that displays an image with a border, resembling a marble. Use for avatars, profile pictures, or decorative elements.
+
+**Key Props**:
+
+- `src` (string | StaticImageData): Image source (URL or imported image)
+- `className` (string): Additional CSS classes (use for sizing)
+
+**Examples**:
+
+```tsx
+import marble1 from "./images/marble1.png";
+
+// Basic marble
+
+
+// Custom size
+
+
+// Multiple marbles
+
+
+
+
+
+```
+
+**Edge Cases**:
+
+- Supports both URL strings and imported images
+- Size controlled via className (Tailwind width classes)
+- Circular shape with border styling
+
+**Storybook Reference**: `stories/Marble.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/numberpad.md b/.cursor/skills/interface-design/rules/numberpad.md
new file mode 100644
index 0000000..385978d
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/numberpad.md
@@ -0,0 +1,53 @@
+---
+title: NumberPad Component
+category: interactive-elements
+tags: numberpad, numeric, input, keypad
+---
+
+## NumberPad
+
+A numeric keypad component that allows users to input numbers and decimals. Includes a delete button and validates input to ensure only valid numbers are entered.
+
+**Key Props**:
+
+- `value` (string): Current numeric value
+- `onChange` (function): Callback when value changes
+- `onLongDeletePress` (function): Callback for long press on delete (clears all)
+- `longPressOptions` (object): Options for long press detection
+ - `threshold` (number): Milliseconds for long press (default: 1500)
+
+**Examples**:
+
+```tsx
+// Basic number pad
+const [value, setValue] = useState("");
+
+
+
+// With display
+
+
+// With long delete press (clear all)
+const handleLongDeletePress = () => {
+ setValue("");
+};
+
+
+```
+
+**Edge Cases**:
+
+- Validates input to ensure only valid numbers
+- Long press on delete button clears entire value
+- Decimal point can only be entered once
+- Delete button removes last character on single press
+
+**Storybook Reference**: `stories/NumberPad.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/pill.md b/.cursor/skills/interface-design/rules/pill.md
new file mode 100644
index 0000000..65d2c1d
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/pill.md
@@ -0,0 +1,32 @@
+---
+title: Pill Component
+category: buttons-actions
+tags: pill, toggle, filter, tag
+---
+
+## Pill
+
+A pill-shaped component that can be used as a toggle or label with a rounded appearance. Use for filters, tags, or toggleable options.
+
+**Key Props**:
+
+- `checked` (boolean): Checked/toggled state
+- `children` (ReactNode): Pill content
+- `asChild` (boolean): Render as child component
+
+**Examples**:
+
+```tsx
+// Basic pill
+Pill Label
+
+// Checked state
+Active
+```
+
+**Edge Cases**:
+
+- Can be used as a controlled component
+- Styling adapts based on checked state
+
+**Storybook Reference**: `stories/Pill.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/progress.md b/.cursor/skills/interface-design/rules/progress.md
new file mode 100644
index 0000000..01e7afb
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/progress.md
@@ -0,0 +1,35 @@
+---
+title: Progress Component
+category: feedback-display
+tags: progress, indicator, loading, bar
+---
+
+## Progress
+
+A progress indicator component that shows the completion status of a task or process. The progress is represented by a horizontal bar that fills from left to right based on the provided value between 0 and 100.
+
+**Key Props**:
+
+- `value` (number): Progress value between 0 and 100
+- `className` (string): Additional CSS classes for styling
+
+**Examples**:
+
+```tsx
+// Basic progress bar
+
+
+// Custom styling
+
+```
+
+**Edge Cases**:
+
+- Value should be between 0 and 100
+- Background and indicator colors can be customized with Tailwind classes
+- Uses `data-value` attribute for styling
+
+**Storybook Reference**: `stories/Progress.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/safeareaview.md b/.cursor/skills/interface-design/rules/safeareaview.md
new file mode 100644
index 0000000..3be0a29
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/safeareaview.md
@@ -0,0 +1,38 @@
+---
+title: SafeAreaView Component
+category: layout-navigation
+tags: safe-area, mobile, insets, notch
+---
+
+## SafeAreaView
+
+A component that provides safe area insets for mobile devices, ensuring content is not obscured by notches, status bars, or home indicators. Includes a hook for accessing insets programmatically.
+
+**Key Components**:
+
+- `SafeAreaView`: Component wrapper
+- `useSafeAreaInsets`: Hook for accessing insets
+
+**Examples**:
+
+```tsx
+// Basic usage
+;
+ Content that respects safe areas
+
+
+// With hook
+function MyComponent() {
+ const insets = useSafeAreaInsets()
+
+ return Content
+}
+```
+
+**Edge Cases**:
+
+- Primarily useful for mobile web apps
+- Automatically handles iOS notch and Android system bars
+- Hook provides programmatic access to inset values
+
+**Storybook Reference**: Not available in stories (utility component)
diff --git a/.cursor/skills/interface-design/rules/skeleton.md b/.cursor/skills/interface-design/rules/skeleton.md
new file mode 100644
index 0000000..9f1d864
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/skeleton.md
@@ -0,0 +1,48 @@
+---
+title: Skeleton Component
+category: feedback-display
+tags: skeleton, loading, placeholder, typography
+---
+
+## Skeleton
+
+Display a placeholder preview of your content before the data gets loaded to reduce load-time frustration. Use while content is loading to improve perceived performance.
+
+**Key Props**:
+
+- `width` (number): Width in pixels
+- `height` (number): Height in pixels
+- `className` (string): Additional CSS classes (use for sizing and styling)
+
+**Subcomponents**:
+
+- `SkeletonTypography`: Skeleton for text content matching Typography variants
+
+**Examples**:
+
+```tsx
+// Basic skeleton
+
+
+// Avatar skeleton
+
+
+// Typography skeleton
+
+
+
+// Complex layout
+
+
+
+
+
+```
+
+**Edge Cases**:
+
+- Use `SkeletonTypography` for text placeholders to match typography styles
+- `lines` prop for SkeletonTypography controls number of text lines
+- Supports all Typography variants: display, heading, subtitle, body, label, number
+
+**Storybook Reference**: `stories/Skeleton.stories.tsx`, `stories/SkeletonTypograhy.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/tabs.md b/.cursor/skills/interface-design/rules/tabs.md
new file mode 100644
index 0000000..84e4104
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/tabs.md
@@ -0,0 +1,72 @@
+---
+title: Tabs Component
+category: layout-navigation
+tags: tabs, navigation, switching, icons
+---
+
+## Tabs
+
+A tab navigation component that allows users to switch between different views or sections. Each tab can have an icon and label, with support for active/inactive icon states.
+
+**Key Components**:
+
+- `Tabs`: Root component
+- `TabItem`: Individual tab item
+
+**Key Props**:
+
+- `value` (string): Currently selected tab value
+- `onValueChange` (function): Callback when tab changes
+- `TabItem` props:
+ - `value` (string): Unique tab identifier
+ - `icon` (ReactNode): Icon for inactive state
+ - `altIcon` (ReactNode): Icon for active state (optional)
+ - `label` (string): Tab label text
+
+**Examples**:
+
+```tsx
+// Basic tabs
+const [value, setValue] = useState("wallet");
+
+
+ } label="Apps" />
+ } label="Wallet" />
+ } label="Contacts" />
+ } label="World ID" />
+
+
+// With active/inactive icons
+
+ }
+ altIcon={ }
+ label="Apps"
+ />
+ }
+ altIcon={ }
+ label="Wallet"
+ />
+
+
+// As links
+
+
+ } altIcon={ } label="Apps" />
+
+
+ } altIcon={ } label="Wallet" />
+
+
+```
+
+**Edge Cases**:
+
+- `altIcon` provides different icon for active state (typically solid version)
+- Can be wrapped in anchor tags for navigation
+- Value must match one of the TabItem values
+
+**Storybook Reference**: `stories/Tabs.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/toast.md b/.cursor/skills/interface-design/rules/toast.md
new file mode 100644
index 0000000..c7e81d2
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/toast.md
@@ -0,0 +1,66 @@
+---
+title: Toast Component
+category: feedback-display
+tags: toast, notification, feedback, haptic
+---
+
+## Toast
+
+Provides a way to display temporary notifications to users. Built on top of Radix UI's Toast primitives and includes haptic feedback for mobile devices. Supports success and error variants.
+
+**Key Components**:
+
+- `Toaster`: Must be included in app root to render toasts
+- `useToast`: Hook for showing and managing toasts
+
+**Toast Methods**:
+
+- `toast.success(props)`: Show a success toast
+- `toast.error(props)`: Show an error toast
+- `dismiss(toastId?)`: Dismiss a specific toast or all toasts
+
+**Toast Props**:
+
+- `title` (string): The message to display
+- `duration` (number): Duration in milliseconds before auto-dismiss (default: 3000)
+
+**Examples**:
+
+```tsx
+import { Toaster, useToast } from '@worldcoin/mini-apps-ui-kit-react'
+
+function MyComponent() {
+ const { toast } = useToast()
+
+ const handleSuccess = () => {
+ toast.success({
+ title: 'Operation completed successfully!',
+ duration: 3000,
+ })
+ }
+
+ const handleError = () => {
+ toast.error({
+ title: 'Something went wrong. Please try again.',
+ duration: 5000,
+ })
+ }
+
+ return (
+
+ Show Success
+ Show Error
+
+
+ )
+}
+```
+
+**Edge Cases**:
+
+- ` ` must be included in app root
+- Toasts automatically dismiss after duration
+- Haptic feedback is included automatically on mobile
+- Multiple toasts can be displayed simultaneously
+
+**Storybook Reference**: `stories/Toast.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/togglegroup.md b/.cursor/skills/interface-design/rules/togglegroup.md
new file mode 100644
index 0000000..788193e
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/togglegroup.md
@@ -0,0 +1,64 @@
+---
+title: ToggleGroup Component
+category: interactive-elements
+tags: togglegroup, toggle, selection, radix-ui
+---
+
+## ToggleGroup
+
+A toggle group component that allows users to select one or multiple options. Built on Radix UI ToggleGroup primitive.
+
+**Key Components**:
+
+- `ToggleGroupRoot`: Root component
+- `ToggleGroupItem`: Individual toggle item
+
+**Key Props**:
+
+- `ToggleGroupRoot`:
+ - `type` ("single" | "multiple"): Selection type
+ - `value`, `onValueChange`: Controlled value
+ - `defaultValue`: Uncontrolled default value
+ - `disabled` (boolean): Disables the group
+ - `loop` (boolean): Keyboard navigation loops
+ - `rovingFocus` (boolean): Gets focus on mount
+ - `dir` ("ltr" | "rtl"): Reading direction
+- `ToggleGroupItem`:
+ - `value` (string): Item value
+ - `disabled` (boolean): Disables individual item
+
+**Examples**:
+
+```tsx
+// Single select
+
+ 1D
+ 1W
+ 1M
+ 1Y
+
+
+// Multiple select
+
+ 1D
+ 1W
+ 1M
+ 1Y
+
+
+// With disabled item
+
+ 1D
+ 1W
+ 1M
+
+```
+
+**Edge Cases**:
+
+- Single type allows one selection, multiple allows many
+- Can be controlled or uncontrolled
+- Individual items can be disabled
+- Supports keyboard navigation
+
+**Storybook Reference**: `stories/ToggleGroup.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/token.md b/.cursor/skills/interface-design/rules/token.md
new file mode 100644
index 0000000..609b424
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/token.md
@@ -0,0 +1,42 @@
+---
+title: Token Component
+category: buttons-actions
+tags: token, cryptocurrency, symbol, display
+---
+
+## Token
+
+A component that displays a token symbol. Use for displaying cryptocurrency or token symbols in lists, balances, or transaction details.
+
+**Key Props**:
+
+- `value` (string): Token symbol (e.g., "BTC", "ETH", "USDC", "WLD")
+- `variant` ("default" | "monochrome"): Display variant
+- `disabled` (boolean): Disabled state
+
+**Examples**:
+
+```tsx
+// Default token
+
+
+// Monochrome variant
+
+
+// Disabled
+
+
+// Multiple tokens
+
+
+
+
+
+```
+
+**Edge Cases**:
+
+- Supported tokens: BTC, ETH, USDC, WLD, DAI, USDT, SUI, DOGE, XRP, SOL, LINK
+- Monochrome variant removes color styling
+
+**Storybook Reference**: `stories/Token.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/topbar.md b/.cursor/skills/interface-design/rules/topbar.md
new file mode 100644
index 0000000..ebb14ce
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/topbar.md
@@ -0,0 +1,60 @@
+---
+title: TopBar Component
+category: layout-navigation
+tags: topbar, navigation, header, mobile
+---
+
+## TopBar
+
+A navigation bar component that displays a title with optional start and end elements. Commonly used at the top of pages or modals to show the current section and navigation controls.
+
+**Key Props**:
+
+- `title` (string): Bar title text
+- `startAdornment` (ReactNode): Element at the start (typically back button)
+- `endAdornment` (ReactNode): Element at the end (typically action button)
+
+**Examples**:
+
+```tsx
+// Basic top bar
+
+
+// With back button
+
+
+
+ }
+/>
+
+// With end action
+
+
+
+ }
+/>
+
+// Both adornments
+ }
+ endAdornment={ }
+/>
+
+// No title, just start adornment
+ } />
+```
+
+**Edge Cases**:
+
+- Title is optional
+- Adornments are typically icon buttons
+- Commonly used in mobile app layouts
+
+**Storybook Reference**: `stories/TopBar.stories.tsx`
diff --git a/.cursor/skills/interface-design/rules/typography.md b/.cursor/skills/interface-design/rules/typography.md
new file mode 100644
index 0000000..eca0915
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/typography.md
@@ -0,0 +1,119 @@
+---
+title: Typography Component
+category: feedback-display
+tags: typography, text, semantic-html, accessibility
+---
+
+## Typography
+
+A comprehensive typography system with multiple variants and levels for consistent text styling across the application. Uses the [TWK Lausanne](https://weltkern.com/typefaces/lausanne) font family and supports semantic HTML through the `as` prop. Supports display, heading, subtitle, body, label, and number variants. **Note**: Ensure you have the proper license to use TWK Lausanne before implementing these typography styles.
+
+**Key Props**:
+
+- `variant` ("display" | "heading" | "subtitle" | "body" | "label" | "number"): Text variant
+- `level` (number): Hierarchy level (1-5 depending on variant)
+- `as` ("p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "span" | "small" | "strong" | "div" | "em"): HTML element to render (default: "p"). Use this prop to ensure semantic HTML structure for accessibility and SEO.
+- `children` (ReactNode): Text content
+
+**When to Use Each Variant**:
+
+- **Display**: Use for hero sections, landing page headlines, large marketing statements, or any prominent text that needs maximum visual impact. Largest text size in the system.
+- **Heading**: Use for page titles, section headers, content hierarchy (h1-h4 equivalent). Creates clear visual hierarchy in documents and pages.
+- **Subtitle**: Use for supporting text directly under headings, descriptions, introductory text, or secondary headings. Provides context without overwhelming.
+- **Body**: Use for paragraphs, general content, readable text blocks, and any standard text content. Default variant for most text content.
+- **Label**: Use for form labels, UI labels, small emphasized text, field names, or any text that needs to stand out but remain compact.
+- **Number**: Use for statistics, metrics, large numeric displays, data visualization labels, or any numeric content that needs emphasis.
+
+**Semantic HTML Mapping**:
+
+The `as` prop allows you to render the Typography component as any semantic HTML element. Here are recommended mappings:
+
+| Variant | Level | Recommended `as` | Semantic Use |
+| ---------- | ----- | --------------------- | ------------------------------ |
+| `display` | 1 | `"h1"` | Main page title, hero headline |
+| `heading` | 1 | `"h1"` | Primary page heading |
+| `heading` | 2 | `"h2"` | Section heading |
+| `heading` | 3 | `"h3"` | Subsection heading |
+| `heading` | 4 | `"h4"` | Minor heading |
+| `subtitle` | 1-4 | `"p"` or `"span"` | Supporting text, descriptions |
+| `body` | 1-4 | `"p"` | Paragraphs, body text |
+| `label` | 1-2 | `"label"` or `"span"` | Form labels, UI labels |
+| `number` | 1-5 | `"span"` or `"div"` | Numeric displays, statistics |
+
+**Best Practices for Semantic HTML**:
+
+- Always use appropriate heading tags (`h1`-`h6`) for headings to maintain document structure
+- Use `h1` only once per page for the main title
+- Use `p` for body text and paragraphs
+- Use `label` when the Typography is associated with a form input
+- Use `span` for inline text that doesn't need its own block
+- Maintain proper heading hierarchy (h1 → h2 → h3, don't skip levels)
+
+**Levels by Variant**:
+
+- `display`: level 1 only
+- `heading`: levels 1-4
+- `subtitle`: levels 1-4
+- `body`: levels 1-4
+- `label`: levels 1-2
+- `number`: levels 1-5
+
+**Examples**:
+
+```tsx
+// Display text (hero section)
+
+ The real human network
+
+
+// Headings with semantic HTML
+
+ Main Page Title
+
+
+ Section Heading
+
+
+ Subsection Heading
+
+
+// Subtitle (supporting text)
+
+ Supporting description text that provides context
+
+
+// Body text (paragraphs)
+
+ Regular paragraph text for body content. This is the default variant.
+
+
+ Smaller body text for secondary content.
+
+
+// Labels (form labels)
+
+ Email Address
+
+
+// Numbers (statistics)
+
+ 483K Trading Volume Today
+
+
+// Inline text (using span)
+
+ Inline text content
+
+```
+
+**Edge Cases**:
+
+- Each variant has specific level ranges - using invalid level combinations will not apply correct styling
+- `as` prop defaults to `"p"` but should be overridden for semantic HTML (e.g., use `"h1"` for main headings)
+- Always use semantic HTML tags (`h1`-`h6` for headings, `p` for paragraphs) for accessibility and SEO
+- Maintain proper heading hierarchy - don't skip levels (e.g., h1 → h3 without h2)
+- Use `h1` only once per page for the main document title
+- Typography system ensures consistent spacing and sizing across all variants
+- Font licensing: TWK Lausanne requires proper licensing - ensure compliance before use
+
+**Storybook Reference**: `stories/Typography.stories.tsx`, `stories/Typography.mdx`
diff --git a/.cursor/skills/interface-design/rules/verificationbadge.md b/.cursor/skills/interface-design/rules/verificationbadge.md
new file mode 100644
index 0000000..476f7f4
--- /dev/null
+++ b/.cursor/skills/interface-design/rules/verificationbadge.md
@@ -0,0 +1,35 @@
+---
+title: VerificationBadge Component
+category: feedback-display
+tags: badge, verification, status, orb
+---
+
+## VerificationBadge
+
+A badge component that displays orb verification status. Indicates whether a person is a verified human (orb verified) with a blue badge, or unverified with a gray badge.
+
+**Key Props**:
+
+- `verified` (boolean): Whether the person is a verified human (orb verified)
+- `className` (string): Additional CSS classes (use for sizing)
+
+**Examples**:
+
+```tsx
+// Verified badge
+
+
+// Unverified badge
+
+
+// Custom size
+
+```
+
+**Edge Cases**:
+
+- Verified state shows blue badge with white icon
+- Unverified state shows gray badge with gray icon
+- Size can be customized with className
+
+**Storybook Reference**: `stories/VerificationBadge.stories.tsx`
diff --git a/.cursor/skills/pass-commit-pr-workflow/SKILL.md b/.cursor/skills/pass-commit-pr-workflow/SKILL.md
new file mode 100644
index 0000000..7d1c3b5
--- /dev/null
+++ b/.cursor/skills/pass-commit-pr-workflow/SKILL.md
@@ -0,0 +1,40 @@
+---
+name: pass-commit-pr-workflow
+description: Enforce a pass-before-commit workflow: only commit and create a pull request after required tests pass. Use when finishing a task that is ready for git commit and PR creation.
+---
+
+# Pass, Then Commit and PR
+
+## Goal
+
+Standardize delivery: test first, then commit, then open PR for each completed task.
+
+## Required Gate
+
+Do not commit or create a PR until required tests pass for the scope.
+
+Required checks:
+
+1. Playwright flow test for the user-facing change.
+2. Any relevant project checks requested by the task.
+
+## Delivery Sequence
+
+1. Run tests and record outcomes.
+2. If tests fail, fix issues and rerun until green.
+3. Stage only relevant files.
+4. Commit with a concise message explaining intent.
+5. Push branch and open PR.
+6. Include test evidence in PR description.
+
+## PR Body Minimum
+
+- `Summary`: what changed and why
+- `Test plan`: checklist with executed Playwright scenario(s)
+- `Result`: pass/fail status per executed check
+
+## Safety Rules
+
+- Never include secrets in commits.
+- Do not bypass checks unless explicitly requested by user.
+- If a check cannot run locally, document the blocker in PR test plan.
diff --git a/.cursor/skills/playwright-test-after-task/SKILL.md b/.cursor/skills/playwright-test-after-task/SKILL.md
new file mode 100644
index 0000000..97e14ad
--- /dev/null
+++ b/.cursor/skills/playwright-test-after-task/SKILL.md
@@ -0,0 +1,32 @@
+---
+name: playwright-test-after-task
+description: Require Playwright validation after completing each implementation task. Use when finishing a coding task, bug fix, or refactor so the app is tested in a real browser before reporting completion.
+---
+
+# Playwright Test After Each Task
+
+## Goal
+
+Ensure every completed implementation task is validated in the running app with Playwright browser automation.
+
+## Required Workflow
+
+1. Confirm the app is running locally (reuse an existing dev server when available).
+2. Run a Playwright browser check that covers the changed user path.
+3. Capture objective evidence of pass/fail (snapshot text, URL checks, or screenshot artifact).
+4. Treat failing Playwright checks as blockers: fix, retest, and only then report completion.
+
+## Minimum Playwright Coverage
+
+- Open the app URL for the task (default `http://localhost:3000` unless project says otherwise).
+- Verify the page loads and key changed UI is visible.
+- Execute at least one interaction tied to the change.
+- Verify the expected post-action state.
+
+## Reporting Format
+
+Use this short format in the final task update:
+
+- `Playwright status`: pass or fail
+- `Flow tested`: the exact user flow exercised
+- `Evidence`: key assertion(s) observed
diff --git a/.cursor/skills/qa-test-plan-maintenance/SKILL.md b/.cursor/skills/qa-test-plan-maintenance/SKILL.md
new file mode 100644
index 0000000..bfda6b8
--- /dev/null
+++ b/.cursor/skills/qa-test-plan-maintenance/SKILL.md
@@ -0,0 +1,41 @@
+---
+name: qa-test-plan-maintenance
+description: Maintain a living QA test plan for every execution and merged code change. Use when implementing features, fixing bugs, or preparing pull requests to add or update test coverage items.
+---
+
+# QA Test Plan Maintenance
+
+## Goal
+
+Keep a persistent QA checklist that grows with each implementation and merge so regression coverage improves over time.
+
+## Required Workflow
+
+1. Find the current test plan document before coding.
+2. Add test items for the current execution scope.
+3. Add new regression items for behavior introduced or changed by merged work.
+4. Mark execution results for each tested item (pass/fail/not-run).
+
+## Test Item Rules
+
+- Each item must be concrete, reproducible, and user-visible.
+- Include preconditions when needed (wallet connected, authenticated, seeded data).
+- Include expected outcome, not just action.
+- Prefer stable, scenario-based wording over implementation details.
+
+## Suggested Item Template
+
+- `ID`: short unique label
+- `Area`: page or feature
+- `Scenario`: user action sequence
+- `Expected`: observable result
+- `Run in this task`: yes/no
+- `Last result`: pass/fail/not-run
+
+## Merge Hygiene
+
+Before opening or updating a PR, verify:
+
+- New behavior has at least one new QA item.
+- Risky paths have regression items.
+- Removed behavior has stale test items cleaned up.
diff --git a/.cursor/skills/vercel-react-best-practices/AGENTS.md b/.cursor/skills/vercel-react-best-practices/AGENTS.md
new file mode 100644
index 0000000..6dff2c3
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/AGENTS.md
@@ -0,0 +1,2197 @@
+# React Best Practices
+
+**Version 1.0.0**
+Vercel Engineering
+January 2026
+
+> **Note:**
+> This document is mainly for agents and LLMs to follow when maintaining,
+> generating, or refactoring React and Next.js codebases at Vercel. Humans
+> may also find it useful, but guidance here is optimized for automation
+> and consistency by AI-assisted workflows.
+
+---
+
+## Abstract
+
+Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.
+
+---
+
+## Table of Contents
+
+1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL**
+ - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed)
+ - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization)
+ - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes)
+ - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations)
+ - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries)
+2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL**
+ - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports)
+ - 2.2 [Conditional Module Loading](#22-conditional-module-loading)
+ - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries)
+ - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components)
+ - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent)
+3. [Server-Side Performance](#3-server-side-performance) — **HIGH**
+ - 3.1 [Cross-Request LRU Caching](#31-cross-request-lru-caching)
+ - 3.2 [Minimize Serialization at RSC Boundaries](#32-minimize-serialization-at-rsc-boundaries)
+ - 3.3 [Parallel Data Fetching with Component Composition](#33-parallel-data-fetching-with-component-composition)
+ - 3.4 [Per-Request Deduplication with React.cache()](#34-per-request-deduplication-with-reactcache)
+ - 3.5 [Use after() for Non-Blocking Operations](#35-use-after-for-non-blocking-operations)
+4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH**
+ - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners)
+ - 4.2 [Use SWR for Automatic Deduplication](#42-use-swr-for-automatic-deduplication)
+5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM**
+ - 5.1 [Defer State Reads to Usage Point](#51-defer-state-reads-to-usage-point)
+ - 5.2 [Extract to Memoized Components](#52-extract-to-memoized-components)
+ - 5.3 [Narrow Effect Dependencies](#53-narrow-effect-dependencies)
+ - 5.4 [Subscribe to Derived State](#54-subscribe-to-derived-state)
+ - 5.5 [Use Functional setState Updates](#55-use-functional-setstate-updates)
+ - 5.6 [Use Lazy State Initialization](#56-use-lazy-state-initialization)
+ - 5.7 [Use Transitions for Non-Urgent Updates](#57-use-transitions-for-non-urgent-updates)
+6. [Rendering Performance](#6-rendering-performance) — **MEDIUM**
+ - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)
+ - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)
+ - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)
+ - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)
+ - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)
+ - 6.6 [Use Activity Component for Show/Hide](#66-use-activity-component-for-showhide)
+ - 6.7 [Use Explicit Conditional Rendering](#67-use-explicit-conditional-rendering)
+7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM**
+ - 7.1 [Batch DOM CSS Changes](#71-batch-dom-css-changes)
+ - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)
+ - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops)
+ - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls)
+ - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls)
+ - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations)
+ - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons)
+ - 7.8 [Early Return from Functions](#78-early-return-from-functions)
+ - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation)
+ - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort)
+ - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups)
+ - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability)
+8. [Advanced Patterns](#8-advanced-patterns) — **LOW**
+ - 8.1 [Store Event Handlers in Refs](#81-store-event-handlers-in-refs)
+ - 8.2 [useLatest for Stable Callback Refs](#82-uselatest-for-stable-callback-refs)
+
+---
+
+## 1. Eliminating Waterfalls
+
+**Impact: CRITICAL**
+
+Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.
+
+### 1.1 Defer Await Until Needed
+
+**Impact: HIGH (avoids blocking unused code paths)**
+
+Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.
+
+**Incorrect: blocks both branches**
+
+```typescript
+async function handleRequest(userId: string, skipProcessing: boolean) {
+ const userData = await fetchUserData(userId)
+
+ if (skipProcessing) {
+ // Returns immediately but still waited for userData
+ return { skipped: true }
+ }
+
+ // Only this branch uses userData
+ return processUserData(userData)
+}
+```
+
+**Correct: only blocks when needed**
+
+```typescript
+async function handleRequest(userId: string, skipProcessing: boolean) {
+ if (skipProcessing) {
+ // Returns immediately without waiting
+ return { skipped: true }
+ }
+
+ // Fetch only when needed
+ const userData = await fetchUserData(userId)
+ return processUserData(userData)
+}
+```
+
+**Another example: early return optimization**
+
+```typescript
+// Incorrect: always fetches permissions
+async function updateResource(resourceId: string, userId: string) {
+ const permissions = await fetchPermissions(userId)
+ const resource = await getResource(resourceId)
+
+ if (!resource) {
+ return { error: 'Not found' }
+ }
+
+ if (!permissions.canEdit) {
+ return { error: 'Forbidden' }
+ }
+
+ return await updateResourceData(resource, permissions)
+}
+
+// Correct: fetches only when needed
+async function updateResource(resourceId: string, userId: string) {
+ const resource = await getResource(resourceId)
+
+ if (!resource) {
+ return { error: 'Not found' }
+ }
+
+ const permissions = await fetchPermissions(userId)
+
+ if (!permissions.canEdit) {
+ return { error: 'Forbidden' }
+ }
+
+ return await updateResourceData(resource, permissions)
+}
+```
+
+This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.
+
+### 1.2 Dependency-Based Parallelization
+
+**Impact: CRITICAL (2-10× improvement)**
+
+For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.
+
+**Incorrect: profile waits for config unnecessarily**
+
+```typescript
+const [user, config] = await Promise.all([fetchUser(), fetchConfig()])
+const profile = await fetchProfile(user.id)
+```
+
+**Correct: config and profile run in parallel**
+
+```typescript
+import { all } from 'better-all'
+
+const { user, config, profile } = await all({
+ async user() {
+ return fetchUser()
+ },
+ async config() {
+ return fetchConfig()
+ },
+ async profile() {
+ return fetchProfile((await this.$.user).id)
+ },
+})
+```
+
+Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)
+
+### 1.3 Prevent Waterfall Chains in API Routes
+
+**Impact: CRITICAL (2-10× improvement)**
+
+In API routes and Server Actions, start independent operations immediately, even if you don't await them yet.
+
+**Incorrect: config waits for auth, data waits for both**
+
+```typescript
+export async function GET(request: Request) {
+ const session = await auth()
+ const config = await fetchConfig()
+ const data = await fetchData(session.user.id)
+ return Response.json({ data, config })
+}
+```
+
+**Correct: auth and config start immediately**
+
+```typescript
+export async function GET(request: Request) {
+ const sessionPromise = auth()
+ const configPromise = fetchConfig()
+ const session = await sessionPromise
+ const [config, data] = await Promise.all([configPromise, fetchData(session.user.id)])
+ return Response.json({ data, config })
+}
+```
+
+For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).
+
+### 1.4 Promise.all() for Independent Operations
+
+**Impact: CRITICAL (2-10× improvement)**
+
+When async operations have no interdependencies, execute them concurrently using `Promise.all()`.
+
+**Incorrect: sequential execution, 3 round trips**
+
+```typescript
+const user = await fetchUser()
+const posts = await fetchPosts()
+const comments = await fetchComments()
+```
+
+**Correct: parallel execution, 1 round trip**
+
+```typescript
+const [user, posts, comments] = await Promise.all([fetchUser(), fetchPosts(), fetchComments()])
+```
+
+### 1.5 Strategic Suspense Boundaries
+
+**Impact: HIGH (faster initial paint)**
+
+Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.
+
+**Incorrect: wrapper blocked by data fetching**
+
+```tsx
+async function Page() {
+ const data = await fetchData() // Blocks entire page
+
+ return (
+
+
Sidebar
+
Header
+
+
+
+
Footer
+
+ )
+}
+```
+
+The entire layout waits for data even though only the middle section needs it.
+
+**Correct: wrapper shows immediately, data streams in**
+
+```tsx
+function Page() {
+ return (
+
+
Sidebar
+
Header
+
+ }>
+
+
+
+
Footer
+
+ )
+}
+
+async function DataDisplay() {
+ const data = await fetchData() // Only blocks this component
+ return {data.content}
+}
+```
+
+Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data.
+
+**Alternative: share promise across components**
+
+```tsx
+function Page() {
+ // Start fetch immediately, but don't await
+ const dataPromise = fetchData()
+
+ return (
+
+
Sidebar
+
Header
+
}>
+
+
+
+
Footer
+
+ )
+}
+
+function DataDisplay({ dataPromise }: { dataPromise: Promise }) {
+ const data = use(dataPromise) // Unwraps the promise
+ return {data.content}
+}
+
+function DataSummary({ dataPromise }: { dataPromise: Promise }) {
+ const data = use(dataPromise) // Reuses the same promise
+ return {data.summary}
+}
+```
+
+Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.
+
+**When NOT to use this pattern:**
+
+- Critical data needed for layout decisions (affects positioning)
+
+- SEO-critical content above the fold
+
+- Small, fast queries where suspense overhead isn't worth it
+
+- When you want to avoid layout shift (loading → content jump)
+
+**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.
+
+---
+
+## 2. Bundle Size Optimization
+
+**Impact: CRITICAL**
+
+Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint.
+
+### 2.1 Avoid Barrel File Imports
+
+**Impact: CRITICAL (200-800ms import cost, slow builds)**
+
+Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).
+
+Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.
+
+**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.
+
+**Incorrect: imports entire library**
+
+```tsx
+import { Check, X, Menu } from 'lucide-react'
+// Loads 1,583 modules, takes ~2.8s extra in dev
+// Runtime cost: 200-800ms on every cold start
+
+import { Button, TextField } from '@mui/material'
+// Loads 2,225 modules, takes ~4.2s extra in dev
+```
+
+**Correct: imports only what you need**
+
+```tsx
+import Check from 'lucide-react/dist/esm/icons/check'
+import X from 'lucide-react/dist/esm/icons/x'
+import Menu from 'lucide-react/dist/esm/icons/menu'
+// Loads only 3 modules (~2KB vs ~1MB)
+
+import Button from '@mui/material/Button'
+import TextField from '@mui/material/TextField'
+// Loads only what you use
+```
+
+**Alternative: Next.js 13.5+**
+
+```js
+// next.config.js - use optimizePackageImports
+module.exports = {
+ experimental: {
+ optimizePackageImports: ['lucide-react', '@mui/material'],
+ },
+}
+
+// Then you can keep the ergonomic barrel imports:
+import { Check, X, Menu } from 'lucide-react'
+// Automatically transformed to direct imports at build time
+```
+
+Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.
+
+Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.
+
+Reference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
+
+### 2.2 Conditional Module Loading
+
+**Impact: HIGH (loads large data only when needed)**
+
+Load large data or modules only when a feature is activated.
+
+**Example: lazy-load animation frames**
+
+```tsx
+function AnimationPlayer({ enabled }: { enabled: boolean }) {
+ const [frames, setFrames] = useState (null)
+
+ useEffect(() => {
+ if (enabled && !frames && typeof window !== 'undefined') {
+ import('./animation-frames.js')
+ .then(mod => setFrames(mod.frames))
+ .catch(() => setEnabled(false))
+ }
+ }, [enabled, frames])
+
+ if (!frames) return
+ return
+}
+```
+
+The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.
+
+### 2.3 Defer Non-Critical Third-Party Libraries
+
+**Impact: MEDIUM (loads after hydration)**
+
+Analytics, logging, and error tracking don't block user interaction. Load them after hydration.
+
+**Incorrect: blocks initial bundle**
+
+```tsx
+import { Analytics } from '@vercel/analytics/react'
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+```
+
+**Correct: loads after hydration**
+
+```tsx
+import dynamic from 'next/dynamic'
+
+const Analytics = dynamic(() => import('@vercel/analytics/react').then(m => m.Analytics), {
+ ssr: false,
+})
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+```
+
+### 2.4 Dynamic Imports for Heavy Components
+
+**Impact: CRITICAL (directly affects TTI and LCP)**
+
+Use `next/dynamic` to lazy-load large components not needed on initial render.
+
+**Incorrect: Monaco bundles with main chunk ~300KB**
+
+```tsx
+import { MonacoEditor } from './monaco-editor'
+
+function CodePanel({ code }: { code: string }) {
+ return
+}
+```
+
+**Correct: Monaco loads on demand**
+
+```tsx
+import dynamic from 'next/dynamic'
+
+const MonacoEditor = dynamic(() => import('./monaco-editor').then(m => m.MonacoEditor), {
+ ssr: false,
+})
+
+function CodePanel({ code }: { code: string }) {
+ return
+}
+```
+
+### 2.5 Preload Based on User Intent
+
+**Impact: MEDIUM (reduces perceived latency)**
+
+Preload heavy bundles before they're needed to reduce perceived latency.
+
+**Example: preload on hover/focus**
+
+```tsx
+function EditorButton({ onClick }: { onClick: () => void }) {
+ const preload = () => {
+ if (typeof window !== 'undefined') {
+ void import('./monaco-editor')
+ }
+ }
+
+ return (
+
+ Open Editor
+
+ )
+}
+```
+
+**Example: preload when feature flag is enabled**
+
+```tsx
+function FlagsProvider({ children, flags }: Props) {
+ useEffect(() => {
+ if (flags.editorEnabled && typeof window !== 'undefined') {
+ void import('./monaco-editor').then(mod => mod.init())
+ }
+ }, [flags.editorEnabled])
+
+ return {children}
+}
+```
+
+The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.
+
+---
+
+## 3. Server-Side Performance
+
+**Impact: HIGH**
+
+Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.
+
+### 3.1 Cross-Request LRU Caching
+
+**Impact: HIGH (caches across requests)**
+
+`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.
+
+**Implementation:**
+
+```typescript
+import { LRUCache } from 'lru-cache'
+
+const cache = new LRUCache({
+ max: 1000,
+ ttl: 5 * 60 * 1000, // 5 minutes
+})
+
+export async function getUser(id: string) {
+ const cached = cache.get(id)
+ if (cached) return cached
+
+ const user = await db.user.findUnique({ where: { id } })
+ cache.set(id, user)
+ return user
+}
+
+// Request 1: DB query, result cached
+// Request 2: cache hit, no DB query
+```
+
+Use when sequential user actions hit multiple endpoints needing the same data within seconds.
+
+**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.
+
+**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.
+
+Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)
+
+### 3.2 Minimize Serialization at RSC Boundaries
+
+**Impact: HIGH (reduces data transfer size)**
+
+The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.
+
+**Incorrect: serializes all 50 fields**
+
+```tsx
+async function Page() {
+ const user = await fetchUser() // 50 fields
+ return
+}
+
+;('use client')
+function Profile({ user }: { user: User }) {
+ return {user.name}
// uses 1 field
+}
+```
+
+**Correct: serializes only 1 field**
+
+```tsx
+async function Page() {
+ const user = await fetchUser()
+ return
+}
+
+;('use client')
+function Profile({ name }: { name: string }) {
+ return {name}
+}
+```
+
+### 3.3 Parallel Data Fetching with Component Composition
+
+**Impact: CRITICAL (eliminates server-side waterfalls)**
+
+React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.
+
+**Incorrect: Sidebar waits for Page's fetch to complete**
+
+```tsx
+export default async function Page() {
+ const header = await fetchHeader()
+ return (
+
+ )
+}
+
+async function Sidebar() {
+ const items = await fetchSidebarItems()
+ return {items.map(renderItem)}
+}
+```
+
+**Correct: both fetch simultaneously**
+
+```tsx
+async function Header() {
+ const data = await fetchHeader()
+ return {data}
+}
+
+async function Sidebar() {
+ const items = await fetchSidebarItems()
+ return {items.map(renderItem)}
+}
+
+export default function Page() {
+ return (
+
+
+
+
+ )
+}
+```
+
+**Alternative with children prop:**
+
+```tsx
+async function Layout({ children }: { children: ReactNode }) {
+ const header = await fetchHeader()
+ return (
+
+
{header}
+ {children}
+
+ )
+}
+
+async function Sidebar() {
+ const items = await fetchSidebarItems()
+ return {items.map(renderItem)}
+}
+
+export default function Page() {
+ return (
+
+
+
+ )
+}
+```
+
+### 3.4 Per-Request Deduplication with React.cache()
+
+**Impact: MEDIUM (deduplicates within request)**
+
+Use `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.
+
+**Usage:**
+
+```typescript
+import { cache } from 'react'
+
+export const getCurrentUser = cache(async () => {
+ const session = await auth()
+ if (!session?.user?.id) return null
+ return await db.user.findUnique({
+ where: { id: session.user.id },
+ })
+})
+```
+
+Within a single request, multiple calls to `getCurrentUser()` execute the query only once.
+
+### 3.5 Use after() for Non-Blocking Operations
+
+**Impact: MEDIUM (faster response times)**
+
+Use Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.
+
+**Incorrect: blocks response**
+
+```tsx
+import { logUserAction } from '@/app/utils'
+
+export async function POST(request: Request) {
+ // Perform mutation
+ await updateDatabase(request)
+
+ // Logging blocks the response
+ const userAgent = request.headers.get('user-agent') || 'unknown'
+ await logUserAction({ userAgent })
+
+ return new Response(JSON.stringify({ status: 'success' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ })
+}
+```
+
+**Correct: non-blocking**
+
+```tsx
+import { after } from 'next/server'
+import { headers, cookies } from 'next/headers'
+import { logUserAction } from '@/app/utils'
+
+export async function POST(request: Request) {
+ // Perform mutation
+ await updateDatabase(request)
+
+ // Log after response is sent
+ after(async () => {
+ const userAgent = (await headers()).get('user-agent') || 'unknown'
+ const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
+
+ logUserAction({ sessionCookie, userAgent })
+ })
+
+ return new Response(JSON.stringify({ status: 'success' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ })
+}
+```
+
+The response is sent immediately while logging happens in the background.
+
+**Common use cases:**
+
+- Analytics tracking
+
+- Audit logging
+
+- Sending notifications
+
+- Cache invalidation
+
+- Cleanup tasks
+
+**Important notes:**
+
+- `after()` runs even if the response fails or redirects
+
+- Works in Server Actions, Route Handlers, and Server Components
+
+Reference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)
+
+---
+
+## 4. Client-Side Data Fetching
+
+**Impact: MEDIUM-HIGH**
+
+Automatic deduplication and efficient data fetching patterns reduce redundant network requests.
+
+### 4.1 Deduplicate Global Event Listeners
+
+**Impact: LOW (single listener for N components)**
+
+Use `useSWRSubscription()` to share global event listeners across component instances.
+
+**Incorrect: N instances = N listeners**
+
+```tsx
+function useKeyboardShortcut(key: string, callback: () => void) {
+ useEffect(() => {
+ const handler = (e: KeyboardEvent) => {
+ if (e.metaKey && e.key === key) {
+ callback()
+ }
+ }
+ window.addEventListener('keydown', handler)
+ return () => window.removeEventListener('keydown', handler)
+ }, [key, callback])
+}
+```
+
+When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.
+
+**Correct: N instances = 1 listener**
+
+```tsx
+import useSWRSubscription from 'swr/subscription'
+
+// Module-level Map to track callbacks per key
+const keyCallbacks = new Map void>>()
+
+function useKeyboardShortcut(key: string, callback: () => void) {
+ // Register this callback in the Map
+ useEffect(() => {
+ if (!keyCallbacks.has(key)) {
+ keyCallbacks.set(key, new Set())
+ }
+ keyCallbacks.get(key)!.add(callback)
+
+ return () => {
+ const set = keyCallbacks.get(key)
+ if (set) {
+ set.delete(callback)
+ if (set.size === 0) {
+ keyCallbacks.delete(key)
+ }
+ }
+ }
+ }, [key, callback])
+
+ useSWRSubscription('global-keydown', () => {
+ const handler = (e: KeyboardEvent) => {
+ if (e.metaKey && keyCallbacks.has(e.key)) {
+ keyCallbacks.get(e.key)!.forEach(cb => cb())
+ }
+ }
+ window.addEventListener('keydown', handler)
+ return () => window.removeEventListener('keydown', handler)
+ })
+}
+
+function Profile() {
+ // Multiple shortcuts will share the same listener
+ useKeyboardShortcut('p', () => {
+ /* ... */
+ })
+ useKeyboardShortcut('k', () => {
+ /* ... */
+ })
+ // ...
+}
+```
+
+### 4.2 Use SWR for Automatic Deduplication
+
+**Impact: MEDIUM-HIGH (automatic deduplication)**
+
+SWR enables request deduplication, caching, and revalidation across component instances.
+
+**Incorrect: no deduplication, each instance fetches**
+
+```tsx
+function UserList() {
+ const [users, setUsers] = useState([])
+ useEffect(() => {
+ fetch('/api/users')
+ .then(r => r.json())
+ .then(setUsers)
+ }, [])
+}
+```
+
+**Correct: multiple instances share one request**
+
+```tsx
+import useSWR from 'swr'
+
+function UserList() {
+ const { data: users } = useSWR('/api/users', fetcher)
+}
+```
+
+**For immutable data:**
+
+```tsx
+import { useImmutableSWR } from '@/lib/swr'
+
+function StaticContent() {
+ const { data } = useImmutableSWR('/api/config', fetcher)
+}
+```
+
+**For mutations:**
+
+```tsx
+import { useSWRMutation } from 'swr/mutation'
+
+function UpdateButton() {
+ const { trigger } = useSWRMutation('/api/user', updateUser)
+ return trigger()}>Update
+}
+```
+
+Reference: [https://swr.vercel.app](https://swr.vercel.app)
+
+---
+
+## 5. Re-render Optimization
+
+**Impact: MEDIUM**
+
+Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.
+
+### 5.1 Defer State Reads to Usage Point
+
+**Impact: MEDIUM (avoids unnecessary subscriptions)**
+
+Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.
+
+**Incorrect: subscribes to all searchParams changes**
+
+```tsx
+function ShareButton({ chatId }: { chatId: string }) {
+ const searchParams = useSearchParams()
+
+ const handleShare = () => {
+ const ref = searchParams.get('ref')
+ shareChat(chatId, { ref })
+ }
+
+ return Share
+}
+```
+
+**Correct: reads on demand, no subscription**
+
+```tsx
+function ShareButton({ chatId }: { chatId: string }) {
+ const handleShare = () => {
+ const params = new URLSearchParams(window.location.search)
+ const ref = params.get('ref')
+ shareChat(chatId, { ref })
+ }
+
+ return Share
+}
+```
+
+### 5.2 Extract to Memoized Components
+
+**Impact: MEDIUM (enables early returns)**
+
+Extract expensive work into memoized components to enable early returns before computation.
+
+**Incorrect: computes avatar even when loading**
+
+```tsx
+function Profile({ user, loading }: Props) {
+ const avatar = useMemo(() => {
+ const id = computeAvatarId(user)
+ return
+ }, [user])
+
+ if (loading) return
+ return {avatar}
+}
+```
+
+**Correct: skips computation when loading**
+
+```tsx
+const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
+ const id = useMemo(() => computeAvatarId(user), [user])
+ return
+})
+
+function Profile({ user, loading }: Props) {
+ if (loading) return
+ return (
+
+
+
+ )
+}
+```
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.
+
+### 5.3 Narrow Effect Dependencies
+
+**Impact: LOW (minimizes effect re-runs)**
+
+Specify primitive dependencies instead of objects to minimize effect re-runs.
+
+**Incorrect: re-runs on any user field change**
+
+```tsx
+useEffect(() => {
+ console.log(user.id)
+}, [user])
+```
+
+**Correct: re-runs only when id changes**
+
+```tsx
+useEffect(() => {
+ console.log(user.id)
+}, [user.id])
+```
+
+**For derived state, compute outside effect:**
+
+```tsx
+// Incorrect: runs on width=767, 766, 765...
+useEffect(() => {
+ if (width < 768) {
+ enableMobileMode()
+ }
+}, [width])
+
+// Correct: runs only on boolean transition
+const isMobile = width < 768
+useEffect(() => {
+ if (isMobile) {
+ enableMobileMode()
+ }
+}, [isMobile])
+```
+
+### 5.4 Subscribe to Derived State
+
+**Impact: MEDIUM (reduces re-render frequency)**
+
+Subscribe to derived boolean state instead of continuous values to reduce re-render frequency.
+
+**Incorrect: re-renders on every pixel change**
+
+```tsx
+function Sidebar() {
+ const width = useWindowWidth() // updates continuously
+ const isMobile = width < 768
+ return
+}
+```
+
+**Correct: re-renders only when boolean changes**
+
+```tsx
+function Sidebar() {
+ const isMobile = useMediaQuery('(max-width: 767px)')
+ return
+}
+```
+
+### 5.5 Use Functional setState Updates
+
+**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**
+
+When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.
+
+**Incorrect: requires state as dependency**
+
+```tsx
+function TodoList() {
+ const [items, setItems] = useState(initialItems)
+
+ // Callback must depend on items, recreated on every items change
+ const addItems = useCallback(
+ (newItems: Item[]) => {
+ setItems([...items, ...newItems])
+ },
+ [items]
+ ) // ❌ items dependency causes recreations
+
+ // Risk of stale closure if dependency is forgotten
+ const removeItem = useCallback((id: string) => {
+ setItems(items.filter(item => item.id !== id))
+ }, []) // ❌ Missing items dependency - will use stale items!
+
+ return
+}
+```
+
+The first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.
+
+**Correct: stable callbacks, no stale closures**
+
+```tsx
+function TodoList() {
+ const [items, setItems] = useState(initialItems)
+
+ // Stable callback, never recreated
+ const addItems = useCallback((newItems: Item[]) => {
+ setItems(curr => [...curr, ...newItems])
+ }, []) // ✅ No dependencies needed
+
+ // Always uses latest state, no stale closure risk
+ const removeItem = useCallback((id: string) => {
+ setItems(curr => curr.filter(item => item.id !== id))
+ }, []) // ✅ Safe and stable
+
+ return
+}
+```
+
+**Benefits:**
+
+1. **Stable callback references** - Callbacks don't need to be recreated when state changes
+
+2. **No stale closures** - Always operates on the latest state value
+
+3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks
+
+4. **Prevents bugs** - Eliminates the most common source of React closure bugs
+
+**When to use functional updates:**
+
+- Any setState that depends on the current state value
+
+- Inside useCallback/useMemo when state is needed
+
+- Event handlers that reference state
+
+- Async operations that update state
+
+**When direct updates are fine:**
+
+- Setting state to a static value: `setCount(0)`
+
+- Setting state from props/arguments only: `setName(newName)`
+
+- State doesn't depend on previous value
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
+
+### 5.6 Use Lazy State Initialization
+
+**Impact: MEDIUM (wasted computation on every render)**
+
+Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.
+
+**Incorrect: runs on every render**
+
+```tsx
+function FilteredList({ items }: { items: Item[] }) {
+ // buildSearchIndex() runs on EVERY render, even after initialization
+ const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
+ const [query, setQuery] = useState('')
+
+ // When query changes, buildSearchIndex runs again unnecessarily
+ return
+}
+
+function UserProfile() {
+ // JSON.parse runs on every render
+ const [settings, setSettings] = useState(JSON.parse(localStorage.getItem('settings') || '{}'))
+
+ return
+}
+```
+
+**Correct: runs only once**
+
+```tsx
+function FilteredList({ items }: { items: Item[] }) {
+ // buildSearchIndex() runs ONLY on initial render
+ const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
+ const [query, setQuery] = useState('')
+
+ return
+}
+
+function UserProfile() {
+ // JSON.parse runs only on initial render
+ const [settings, setSettings] = useState(() => {
+ const stored = localStorage.getItem('settings')
+ return stored ? JSON.parse(stored) : {}
+ })
+
+ return
+}
+```
+
+Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.
+
+For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
+
+### 5.7 Use Transitions for Non-Urgent Updates
+
+**Impact: MEDIUM (maintains UI responsiveness)**
+
+Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness.
+
+**Incorrect: blocks UI on every scroll**
+
+```tsx
+function ScrollTracker() {
+ const [scrollY, setScrollY] = useState(0)
+ useEffect(() => {
+ const handler = () => setScrollY(window.scrollY)
+ window.addEventListener('scroll', handler, { passive: true })
+ return () => window.removeEventListener('scroll', handler)
+ }, [])
+}
+```
+
+**Correct: non-blocking updates**
+
+```tsx
+import { startTransition } from 'react'
+
+function ScrollTracker() {
+ const [scrollY, setScrollY] = useState(0)
+ useEffect(() => {
+ const handler = () => {
+ startTransition(() => setScrollY(window.scrollY))
+ }
+ window.addEventListener('scroll', handler, { passive: true })
+ return () => window.removeEventListener('scroll', handler)
+ }, [])
+}
+```
+
+---
+
+## 6. Rendering Performance
+
+**Impact: MEDIUM**
+
+Optimizing the rendering process reduces the work the browser needs to do.
+
+### 6.1 Animate SVG Wrapper Instead of SVG Element
+
+**Impact: LOW (enables hardware acceleration)**
+
+Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `` and animate the wrapper instead.
+
+**Incorrect: animating SVG directly - no hardware acceleration**
+
+```tsx
+function LoadingSpinner() {
+ return (
+
+
+
+ )
+}
+```
+
+**Correct: animating wrapper div - hardware accelerated**
+
+```tsx
+function LoadingSpinner() {
+ return (
+
+
+
+
+
+ )
+}
+```
+
+This applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations.
+
+### 6.2 CSS content-visibility for Long Lists
+
+**Impact: HIGH (faster initial render)**
+
+Apply `content-visibility: auto` to defer off-screen rendering.
+
+**CSS:**
+
+```css
+.message-item {
+ content-visibility: auto;
+ contain-intrinsic-size: 0 80px;
+}
+```
+
+**Example:**
+
+```tsx
+function MessageList({ messages }: { messages: Message[] }) {
+ return (
+
+ {messages.map(msg => (
+
+ ))}
+
+ )
+}
+```
+
+For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).
+
+### 6.3 Hoist Static JSX Elements
+
+**Impact: LOW (avoids re-creation)**
+
+Extract static JSX outside components to avoid re-creation.
+
+**Incorrect: recreates element every render**
+
+```tsx
+function LoadingSkeleton() {
+ return
+}
+
+function Container() {
+ return
{loading && }
+}
+```
+
+**Correct: reuses same element**
+
+```tsx
+const loadingSkeleton =
+
+function Container() {
+ return
{loading && loadingSkeleton}
+}
+```
+
+This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.
+
+### 6.4 Optimize SVG Precision
+
+**Impact: LOW (reduces file size)**
+
+Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.
+
+**Incorrect: excessive precision**
+
+```svg
+
+```
+
+**Correct: 1 decimal place**
+
+```svg
+
+```
+
+**Automate with SVGO:**
+
+```bash
+npx svgo --precision=1 --multipass icon.svg
+```
+
+### 6.5 Prevent Hydration Mismatch Without Flickering
+
+**Impact: MEDIUM (avoids visual flicker and hydration errors)**
+
+When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.
+
+**Incorrect: breaks SSR**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ // localStorage is not available on server - throws error
+ const theme = localStorage.getItem('theme') || 'light'
+
+ return
{children}
+}
+```
+
+Server-side rendering will fail because `localStorage` is undefined.
+
+**Incorrect: visual flickering**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ const [theme, setTheme] = useState('light')
+
+ useEffect(() => {
+ // Runs after hydration - causes visible flash
+ const stored = localStorage.getItem('theme')
+ if (stored) {
+ setTheme(stored)
+ }
+ }, [])
+
+ return
{children}
+}
+```
+
+Component first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content.
+
+**Correct: no flicker, no hydration mismatch**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ return (
+ <>
+
{children}
+
+ >
+ )
+}
+```
+
+The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.
+
+This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.
+
+### 6.6 Use Activity Component for Show/Hide
+
+**Impact: MEDIUM (preserves state/DOM)**
+
+Use React's `
` to preserve state/DOM for expensive components that frequently toggle visibility.
+
+**Usage:**
+
+```tsx
+import { Activity } from 'react'
+
+function Dropdown({ isOpen }: Props) {
+ return (
+
+
+
+ )
+}
+```
+
+Avoids expensive re-renders and state loss.
+
+### 6.7 Use Explicit Conditional Rendering
+
+**Impact: LOW (prevents rendering 0 or NaN)**
+
+Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.
+
+**Incorrect: renders "0" when count is 0**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return {count && {count} }
+}
+
+// When count = 0, renders: 0
+// When count = 5, renders: 5
+```
+
+**Correct: renders nothing when count is 0**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return {count > 0 ? {count} : null}
+}
+
+// When count = 0, renders:
+// When count = 5, renders: 5
+```
+
+---
+
+## 7. JavaScript Performance
+
+**Impact: LOW-MEDIUM**
+
+Micro-optimizations for hot paths can add up to meaningful improvements.
+
+### 7.1 Batch DOM CSS Changes
+
+**Impact: MEDIUM (reduces reflows/repaints)**
+
+Avoid changing styles one property at a time. Group multiple CSS changes together via classes or `cssText` to minimize browser reflows.
+
+**Incorrect: multiple reflows**
+
+```typescript
+function updateElementStyles(element: HTMLElement) {
+ // Each line triggers a reflow
+ element.style.width = '100px'
+ element.style.height = '200px'
+ element.style.backgroundColor = 'blue'
+ element.style.border = '1px solid black'
+}
+```
+
+**Correct: add class - single reflow**
+
+```typescript
+// CSS file
+.highlighted-box {
+ width: 100px;
+ height: 200px;
+ background-color: blue;
+ border: 1px solid black;
+}
+
+// JavaScript
+function updateElementStyles(element: HTMLElement) {
+ element.classList.add('highlighted-box')
+}
+```
+
+**Correct: change cssText - single reflow**
+
+```typescript
+function updateElementStyles(element: HTMLElement) {
+ element.style.cssText = `
+ width: 100px;
+ height: 200px;
+ background-color: blue;
+ border: 1px solid black;
+ `
+}
+```
+
+**React example:**
+
+```tsx
+// Incorrect: changing styles one by one
+function Box({ isHighlighted }: { isHighlighted: boolean }) {
+ const ref = useRef(null)
+
+ useEffect(() => {
+ if (ref.current && isHighlighted) {
+ ref.current.style.width = '100px'
+ ref.current.style.height = '200px'
+ ref.current.style.backgroundColor = 'blue'
+ }
+ }, [isHighlighted])
+
+ return Content
+}
+
+// Correct: toggle class
+function Box({ isHighlighted }: { isHighlighted: boolean }) {
+ return Content
+}
+```
+
+Prefer CSS classes over inline styles when possible. Classes are cached by the browser and provide better separation of concerns.
+
+### 7.2 Build Index Maps for Repeated Lookups
+
+**Impact: LOW-MEDIUM (1M ops to 2K ops)**
+
+Multiple `.find()` calls by the same key should use a Map.
+
+**Incorrect (O(n) per lookup):**
+
+```typescript
+function processOrders(orders: Order[], users: User[]) {
+ return orders.map(order => ({
+ ...order,
+ user: users.find(u => u.id === order.userId),
+ }))
+}
+```
+
+**Correct (O(1) per lookup):**
+
+```typescript
+function processOrders(orders: Order[], users: User[]) {
+ const userById = new Map(users.map(u => [u.id, u]))
+
+ return orders.map(order => ({
+ ...order,
+ user: userById.get(order.userId),
+ }))
+}
+```
+
+Build map once (O(n)), then all lookups are O(1).
+
+For 1000 orders × 1000 users: 1M ops → 2K ops.
+
+### 7.3 Cache Property Access in Loops
+
+**Impact: LOW-MEDIUM (reduces lookups)**
+
+Cache object property lookups in hot paths.
+
+**Incorrect: 3 lookups × N iterations**
+
+```typescript
+for (let i = 0; i < arr.length; i++) {
+ process(obj.config.settings.value)
+}
+```
+
+**Correct: 1 lookup total**
+
+```typescript
+const value = obj.config.settings.value
+const len = arr.length
+for (let i = 0; i < len; i++) {
+ process(value)
+}
+```
+
+### 7.4 Cache Repeated Function Calls
+
+**Impact: MEDIUM (avoid redundant computation)**
+
+Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.
+
+**Incorrect: redundant computation**
+
+```typescript
+function ProjectList({ projects }: { projects: Project[] }) {
+ return (
+
+ {projects.map(project => {
+ // slugify() called 100+ times for same project names
+ const slug = slugify(project.name)
+
+ return
+ })}
+
+ )
+}
+```
+
+**Correct: cached results**
+
+```typescript
+// Module-level cache
+const slugifyCache = new Map()
+
+function cachedSlugify(text: string): string {
+ if (slugifyCache.has(text)) {
+ return slugifyCache.get(text)!
+ }
+ const result = slugify(text)
+ slugifyCache.set(text, result)
+ return result
+}
+
+function ProjectList({ projects }: { projects: Project[] }) {
+ return (
+
+ {projects.map(project => {
+ // Computed only once per unique project name
+ const slug = cachedSlugify(project.name)
+
+ return
+ })}
+
+ )
+}
+```
+
+**Simpler pattern for single-value functions:**
+
+```typescript
+let isLoggedInCache: boolean | null = null
+
+function isLoggedIn(): boolean {
+ if (isLoggedInCache !== null) {
+ return isLoggedInCache
+ }
+
+ isLoggedInCache = document.cookie.includes('auth=')
+ return isLoggedInCache
+}
+
+// Clear cache when auth changes
+function onAuthChange() {
+ isLoggedInCache = null
+}
+```
+
+Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
+
+Reference: [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
+
+### 7.5 Cache Storage API Calls
+
+**Impact: LOW-MEDIUM (reduces expensive I/O)**
+
+`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.
+
+**Incorrect: reads storage on every call**
+
+```typescript
+function getTheme() {
+ return localStorage.getItem('theme') ?? 'light'
+}
+// Called 10 times = 10 storage reads
+```
+
+**Correct: Map cache**
+
+```typescript
+const storageCache = new Map()
+
+function getLocalStorage(key: string) {
+ if (!storageCache.has(key)) {
+ storageCache.set(key, localStorage.getItem(key))
+ }
+ return storageCache.get(key)
+}
+
+function setLocalStorage(key: string, value: string) {
+ localStorage.setItem(key, value)
+ storageCache.set(key, value) // keep cache in sync
+}
+```
+
+Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
+
+**Cookie caching:**
+
+```typescript
+let cookieCache: Record | null = null
+
+function getCookie(name: string) {
+ if (!cookieCache) {
+ cookieCache = Object.fromEntries(document.cookie.split('; ').map(c => c.split('=')))
+ }
+ return cookieCache[name]
+}
+```
+
+**Important: invalidate on external changes**
+
+```typescript
+window.addEventListener('storage', e => {
+ if (e.key) storageCache.delete(e.key)
+})
+
+document.addEventListener('visibilitychange', () => {
+ if (document.visibilityState === 'visible') {
+ storageCache.clear()
+ }
+})
+```
+
+If storage can change externally (another tab, server-set cookies), invalidate cache:
+
+### 7.6 Combine Multiple Array Iterations
+
+**Impact: LOW-MEDIUM (reduces iterations)**
+
+Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
+
+**Incorrect: 3 iterations**
+
+```typescript
+const admins = users.filter(u => u.isAdmin)
+const testers = users.filter(u => u.isTester)
+const inactive = users.filter(u => !u.isActive)
+```
+
+**Correct: 1 iteration**
+
+```typescript
+const admins: User[] = []
+const testers: User[] = []
+const inactive: User[] = []
+
+for (const user of users) {
+ if (user.isAdmin) admins.push(user)
+ if (user.isTester) testers.push(user)
+ if (!user.isActive) inactive.push(user)
+}
+```
+
+### 7.7 Early Length Check for Array Comparisons
+
+**Impact: MEDIUM-HIGH (avoids expensive operations when lengths differ)**
+
+When comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.
+
+In real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).
+
+**Incorrect: always runs expensive comparison**
+
+```typescript
+function hasChanges(current: string[], original: string[]) {
+ // Always sorts and joins, even when lengths differ
+ return current.sort().join() !== original.sort().join()
+}
+```
+
+Two O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.
+
+**Correct (O(1) length check first):**
+
+```typescript
+function hasChanges(current: string[], original: string[]) {
+ // Early return if lengths differ
+ if (current.length !== original.length) {
+ return true
+ }
+ // Only sort/join when lengths match
+ const currentSorted = current.toSorted()
+ const originalSorted = original.toSorted()
+ for (let i = 0; i < currentSorted.length; i++) {
+ if (currentSorted[i] !== originalSorted[i]) {
+ return true
+ }
+ }
+ return false
+}
+```
+
+This new approach is more efficient because:
+
+- It avoids the overhead of sorting and joining the arrays when lengths differ
+
+- It avoids consuming memory for the joined strings (especially important for large arrays)
+
+- It avoids mutating the original arrays
+
+- It returns early when a difference is found
+
+### 7.8 Early Return from Functions
+
+**Impact: LOW-MEDIUM (avoids unnecessary computation)**
+
+Return early when result is determined to skip unnecessary processing.
+
+**Incorrect: processes all items even after finding answer**
+
+```typescript
+function validateUsers(users: User[]) {
+ let hasError = false
+ let errorMessage = ''
+
+ for (const user of users) {
+ if (!user.email) {
+ hasError = true
+ errorMessage = 'Email required'
+ }
+ if (!user.name) {
+ hasError = true
+ errorMessage = 'Name required'
+ }
+ // Continues checking all users even after error found
+ }
+
+ return hasError ? { valid: false, error: errorMessage } : { valid: true }
+}
+```
+
+**Correct: returns immediately on first error**
+
+```typescript
+function validateUsers(users: User[]) {
+ for (const user of users) {
+ if (!user.email) {
+ return { valid: false, error: 'Email required' }
+ }
+ if (!user.name) {
+ return { valid: false, error: 'Name required' }
+ }
+ }
+
+ return { valid: true }
+}
+```
+
+### 7.9 Hoist RegExp Creation
+
+**Impact: LOW-MEDIUM (avoids recreation)**
+
+Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.
+
+**Incorrect: new RegExp every render**
+
+```tsx
+function Highlighter({ text, query }: Props) {
+ const regex = new RegExp(`(${query})`, 'gi')
+ const parts = text.split(regex)
+ return <>{parts.map((part, i) => ...)}>
+}
+```
+
+**Correct: memoize or hoist**
+
+```tsx
+const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+
+function Highlighter({ text, query }: Props) {
+ const regex = useMemo(
+ () => new RegExp(`(${escapeRegex(query)})`, 'gi'),
+ [query]
+ )
+ const parts = text.split(regex)
+ return <>{parts.map((part, i) => ...)}>
+}
+```
+
+**Warning: global regex has mutable state**
+
+```typescript
+const regex = /foo/g
+regex.test('foo') // true, lastIndex = 3
+regex.test('foo') // false, lastIndex = 0
+```
+
+Global regex (`/g`) has mutable `lastIndex` state:
+
+### 7.10 Use Loop for Min/Max Instead of Sort
+
+**Impact: LOW (O(n) instead of O(n log n))**
+
+Finding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.
+
+**Incorrect (O(n log n) - sort to find latest):**
+
+```typescript
+interface Project {
+ id: string
+ name: string
+ updatedAt: number
+}
+
+function getLatestProject(projects: Project[]) {
+ const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)
+ return sorted[0]
+}
+```
+
+Sorts the entire array just to find the maximum value.
+
+**Incorrect (O(n log n) - sort for oldest and newest):**
+
+```typescript
+function getOldestAndNewest(projects: Project[]) {
+ const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)
+ return { oldest: sorted[0], newest: sorted[sorted.length - 1] }
+}
+```
+
+Still sorts unnecessarily when only min/max are needed.
+
+**Correct (O(n) - single loop):**
+
+```typescript
+function getLatestProject(projects: Project[]) {
+ if (projects.length === 0) return null
+
+ let latest = projects[0]
+
+ for (let i = 1; i < projects.length; i++) {
+ if (projects[i].updatedAt > latest.updatedAt) {
+ latest = projects[i]
+ }
+ }
+
+ return latest
+}
+
+function getOldestAndNewest(projects: Project[]) {
+ if (projects.length === 0) return { oldest: null, newest: null }
+
+ let oldest = projects[0]
+ let newest = projects[0]
+
+ for (let i = 1; i < projects.length; i++) {
+ if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]
+ if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]
+ }
+
+ return { oldest, newest }
+}
+```
+
+Single pass through the array, no copying, no sorting.
+
+**Alternative: Math.min/Math.max for small arrays**
+
+```typescript
+const numbers = [5, 2, 8, 1, 9]
+const min = Math.min(...numbers)
+const max = Math.max(...numbers)
+```
+
+This works for small arrays but can be slower for very large arrays due to spread operator limitations. Use the loop approach for reliability.
+
+### 7.11 Use Set/Map for O(1) Lookups
+
+**Impact: LOW-MEDIUM (O(n) to O(1))**
+
+Convert arrays to Set/Map for repeated membership checks.
+
+**Incorrect (O(n) per check):**
+
+```typescript
+const allowedIds = ['a', 'b', 'c', ...]
+items.filter(item => allowedIds.includes(item.id))
+```
+
+**Correct (O(1) per check):**
+
+```typescript
+const allowedIds = new Set(['a', 'b', 'c', ...])
+items.filter(item => allowedIds.has(item.id))
+```
+
+### 7.12 Use toSorted() Instead of sort() for Immutability
+
+**Impact: MEDIUM-HIGH (prevents mutation bugs in React state)**
+
+`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation.
+
+**Incorrect: mutates original array**
+
+```typescript
+function UserList({ users }: { users: User[] }) {
+ // Mutates the users prop array!
+ const sorted = useMemo(
+ () => users.sort((a, b) => a.name.localeCompare(b.name)),
+ [users]
+ )
+ return {sorted.map(renderUser)}
+}
+```
+
+**Correct: creates new array**
+
+```typescript
+function UserList({ users }: { users: User[] }) {
+ // Creates new sorted array, original unchanged
+ const sorted = useMemo(
+ () => users.toSorted((a, b) => a.name.localeCompare(b.name)),
+ [users]
+ )
+ return {sorted.map(renderUser)}
+}
+```
+
+**Why this matters in React:**
+
+1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only
+
+2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior
+
+**Browser support: fallback for older browsers**
+
+```typescript
+// Fallback for older browsers
+const sorted = [...items].sort((a, b) => a.value - b.value)
+```
+
+`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:
+
+**Other immutable array methods:**
+
+- `.toSorted()` - immutable sort
+
+- `.toReversed()` - immutable reverse
+
+- `.toSpliced()` - immutable splice
+
+- `.with()` - immutable element replacement
+
+---
+
+## 8. Advanced Patterns
+
+**Impact: LOW**
+
+Advanced patterns for specific cases that require careful implementation.
+
+### 8.1 Store Event Handlers in Refs
+
+**Impact: LOW (stable subscriptions)**
+
+Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.
+
+**Incorrect: re-subscribes on every render**
+
+```tsx
+function useWindowEvent(event: string, handler: () => void) {
+ useEffect(() => {
+ window.addEventListener(event, handler)
+ return () => window.removeEventListener(event, handler)
+ }, [event, handler])
+}
+```
+
+**Correct: stable subscription**
+
+```tsx
+import { useEffectEvent } from 'react'
+
+function useWindowEvent(event: string, handler: () => void) {
+ const onEvent = useEffectEvent(handler)
+
+ useEffect(() => {
+ window.addEventListener(event, onEvent)
+ return () => window.removeEventListener(event, onEvent)
+ }, [event])
+}
+```
+
+**Alternative: use `useEffectEvent` if you're on latest React:**
+
+`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.
+
+### 8.2 useLatest for Stable Callback Refs
+
+**Impact: LOW (prevents effect re-runs)**
+
+Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.
+
+**Implementation:**
+
+```typescript
+function useLatest(value: T) {
+ const ref = useRef(value)
+ useEffect(() => {
+ ref.current = value
+ }, [value])
+ return ref
+}
+```
+
+**Incorrect: effect re-runs on every callback change**
+
+```tsx
+function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
+ const [query, setQuery] = useState('')
+
+ useEffect(() => {
+ const timeout = setTimeout(() => onSearch(query), 300)
+ return () => clearTimeout(timeout)
+ }, [query, onSearch])
+}
+```
+
+**Correct: stable effect, fresh callback**
+
+```tsx
+function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
+ const [query, setQuery] = useState('')
+ const onSearchRef = useLatest(onSearch)
+
+ useEffect(() => {
+ const timeout = setTimeout(() => onSearchRef.current(query), 300)
+ return () => clearTimeout(timeout)
+ }, [query])
+}
+```
+
+---
+
+## References
+
+1. [https://react.dev](https://react.dev)
+2. [https://nextjs.org](https://nextjs.org)
+3. [https://swr.vercel.app](https://swr.vercel.app)
+4. [https://github.com/shuding/better-all](https://github.com/shuding/better-all)
+5. [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)
+6. [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
+7. [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
diff --git a/.cursor/skills/vercel-react-best-practices/SKILL.md b/.cursor/skills/vercel-react-best-practices/SKILL.md
new file mode 100644
index 0000000..194bb4b
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/SKILL.md
@@ -0,0 +1,127 @@
+---
+name: vercel-react-best-practices
+description: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
+license: MIT
+metadata:
+ author: vercel
+ version: '1.0.0'
+---
+
+# Vercel React Best Practices
+
+Comprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 45 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
+
+## When to Apply
+
+Reference these guidelines when:
+
+- Writing new React components or Next.js pages
+- Implementing data fetching (client or server-side)
+- Reviewing code for performance issues
+- Refactoring existing React/Next.js code
+- Optimizing bundle size or load times
+
+## Rule Categories by Priority
+
+| Priority | Category | Impact | Prefix |
+| -------- | ------------------------- | ----------- | ------------ |
+| 1 | Eliminating Waterfalls | CRITICAL | `async-` |
+| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |
+| 3 | Server-Side Performance | HIGH | `server-` |
+| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |
+| 5 | Re-render Optimization | MEDIUM | `rerender-` |
+| 6 | Rendering Performance | MEDIUM | `rendering-` |
+| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |
+| 8 | Advanced Patterns | LOW | `advanced-` |
+
+## Quick Reference
+
+### 1. Eliminating Waterfalls (CRITICAL)
+
+- `async-defer-await` - Move await into branches where actually used
+- `async-parallel` - Use Promise.all() for independent operations
+- `async-dependencies` - Use better-all for partial dependencies
+- `async-api-routes` - Start promises early, await late in API routes
+- `async-suspense-boundaries` - Use Suspense to stream content
+
+### 2. Bundle Size Optimization (CRITICAL)
+
+- `bundle-barrel-imports` - Import directly, avoid barrel files
+- `bundle-dynamic-imports` - Use next/dynamic for heavy components
+- `bundle-defer-third-party` - Load analytics/logging after hydration
+- `bundle-conditional` - Load modules only when feature is activated
+- `bundle-preload` - Preload on hover/focus for perceived speed
+
+### 3. Server-Side Performance (HIGH)
+
+- `server-cache-react` - Use React.cache() for per-request deduplication
+- `server-cache-lru` - Use LRU cache for cross-request caching
+- `server-serialization` - Minimize data passed to client components
+- `server-parallel-fetching` - Restructure components to parallelize fetches
+- `server-after-nonblocking` - Use after() for non-blocking operations
+
+### 4. Client-Side Data Fetching (MEDIUM-HIGH)
+
+- `client-swr-dedup` - Use SWR for automatic request deduplication
+- `client-event-listeners` - Deduplicate global event listeners
+
+### 5. Re-render Optimization (MEDIUM)
+
+- `rerender-defer-reads` - Don't subscribe to state only used in callbacks
+- `rerender-memo` - Extract expensive work into memoized components
+- `rerender-dependencies` - Use primitive dependencies in effects
+- `rerender-derived-state` - Subscribe to derived booleans, not raw values
+- `rerender-functional-setstate` - Use functional setState for stable callbacks
+- `rerender-lazy-state-init` - Pass function to useState for expensive values
+- `rerender-transitions` - Use startTransition for non-urgent updates
+
+### 6. Rendering Performance (MEDIUM)
+
+- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element
+- `rendering-content-visibility` - Use content-visibility for long lists
+- `rendering-hoist-jsx` - Extract static JSX outside components
+- `rendering-svg-precision` - Reduce SVG coordinate precision
+- `rendering-hydration-no-flicker` - Use inline script for client-only data
+- `rendering-activity` - Use Activity component for show/hide
+- `rendering-conditional-render` - Use ternary, not && for conditionals
+
+### 7. JavaScript Performance (LOW-MEDIUM)
+
+- `js-batch-dom-css` - Group CSS changes via classes or cssText
+- `js-index-maps` - Build Map for repeated lookups
+- `js-cache-property-access` - Cache object properties in loops
+- `js-cache-function-results` - Cache function results in module-level Map
+- `js-cache-storage` - Cache localStorage/sessionStorage reads
+- `js-combine-iterations` - Combine multiple filter/map into one loop
+- `js-length-check-first` - Check array length before expensive comparison
+- `js-early-exit` - Return early from functions
+- `js-hoist-regexp` - Hoist RegExp creation outside loops
+- `js-min-max-loop` - Use loop for min/max instead of sort
+- `js-set-map-lookups` - Use Set/Map for O(1) lookups
+- `js-tosorted-immutable` - Use toSorted() for immutability
+
+### 8. Advanced Patterns (LOW)
+
+- `advanced-event-handler-refs` - Store event handlers in refs
+- `advanced-use-latest` - useLatest for stable callback refs
+
+## How to Use
+
+Read individual rule files for detailed explanations and code examples:
+
+```
+rules/async-parallel.md
+rules/bundle-barrel-imports.md
+rules/_sections.md
+```
+
+Each rule file contains:
+
+- Brief explanation of why it matters
+- Incorrect code example with explanation
+- Correct code example with explanation
+- Additional context and references
+
+## Full Compiled Document
+
+For the complete guide with all rules expanded: `AGENTS.md`
diff --git a/.cursor/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md b/.cursor/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md
new file mode 100644
index 0000000..3a45152
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md
@@ -0,0 +1,55 @@
+---
+title: Store Event Handlers in Refs
+impact: LOW
+impactDescription: stable subscriptions
+tags: advanced, hooks, refs, event-handlers, optimization
+---
+
+## Store Event Handlers in Refs
+
+Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.
+
+**Incorrect (re-subscribes on every render):**
+
+```tsx
+function useWindowEvent(event: string, handler: () => void) {
+ useEffect(() => {
+ window.addEventListener(event, handler)
+ return () => window.removeEventListener(event, handler)
+ }, [event, handler])
+}
+```
+
+**Correct (stable subscription):**
+
+```tsx
+function useWindowEvent(event: string, handler: () => void) {
+ const handlerRef = useRef(handler)
+ useEffect(() => {
+ handlerRef.current = handler
+ }, [handler])
+
+ useEffect(() => {
+ const listener = () => handlerRef.current()
+ window.addEventListener(event, listener)
+ return () => window.removeEventListener(event, listener)
+ }, [event])
+}
+```
+
+**Alternative: use `useEffectEvent` if you're on latest React:**
+
+```tsx
+import { useEffectEvent } from 'react'
+
+function useWindowEvent(event: string, handler: () => void) {
+ const onEvent = useEffectEvent(handler)
+
+ useEffect(() => {
+ window.addEventListener(event, onEvent)
+ return () => window.removeEventListener(event, onEvent)
+ }, [event])
+}
+```
+
+`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/advanced-use-latest.md b/.cursor/skills/vercel-react-best-practices/rules/advanced-use-latest.md
new file mode 100644
index 0000000..3facdf2
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/advanced-use-latest.md
@@ -0,0 +1,49 @@
+---
+title: useLatest for Stable Callback Refs
+impact: LOW
+impactDescription: prevents effect re-runs
+tags: advanced, hooks, useLatest, refs, optimization
+---
+
+## useLatest for Stable Callback Refs
+
+Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.
+
+**Implementation:**
+
+```typescript
+function useLatest(value: T) {
+ const ref = useRef(value)
+ useEffect(() => {
+ ref.current = value
+ }, [value])
+ return ref
+}
+```
+
+**Incorrect (effect re-runs on every callback change):**
+
+```tsx
+function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
+ const [query, setQuery] = useState('')
+
+ useEffect(() => {
+ const timeout = setTimeout(() => onSearch(query), 300)
+ return () => clearTimeout(timeout)
+ }, [query, onSearch])
+}
+```
+
+**Correct (stable effect, fresh callback):**
+
+```tsx
+function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
+ const [query, setQuery] = useState('')
+ const onSearchRef = useLatest(onSearch)
+
+ useEffect(() => {
+ const timeout = setTimeout(() => onSearchRef.current(query), 300)
+ return () => clearTimeout(timeout)
+ }, [query])
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/async-api-routes.md b/.cursor/skills/vercel-react-best-practices/rules/async-api-routes.md
new file mode 100644
index 0000000..f07097b
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/async-api-routes.md
@@ -0,0 +1,35 @@
+---
+title: Prevent Waterfall Chains in API Routes
+impact: CRITICAL
+impactDescription: 2-10× improvement
+tags: api-routes, server-actions, waterfalls, parallelization
+---
+
+## Prevent Waterfall Chains in API Routes
+
+In API routes and Server Actions, start independent operations immediately, even if you don't await them yet.
+
+**Incorrect (config waits for auth, data waits for both):**
+
+```typescript
+export async function GET(request: Request) {
+ const session = await auth()
+ const config = await fetchConfig()
+ const data = await fetchData(session.user.id)
+ return Response.json({ data, config })
+}
+```
+
+**Correct (auth and config start immediately):**
+
+```typescript
+export async function GET(request: Request) {
+ const sessionPromise = auth()
+ const configPromise = fetchConfig()
+ const session = await sessionPromise
+ const [config, data] = await Promise.all([configPromise, fetchData(session.user.id)])
+ return Response.json({ data, config })
+}
+```
+
+For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).
diff --git a/.cursor/skills/vercel-react-best-practices/rules/async-defer-await.md b/.cursor/skills/vercel-react-best-practices/rules/async-defer-await.md
new file mode 100644
index 0000000..6697a38
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/async-defer-await.md
@@ -0,0 +1,80 @@
+---
+title: Defer Await Until Needed
+impact: HIGH
+impactDescription: avoids blocking unused code paths
+tags: async, await, conditional, optimization
+---
+
+## Defer Await Until Needed
+
+Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.
+
+**Incorrect (blocks both branches):**
+
+```typescript
+async function handleRequest(userId: string, skipProcessing: boolean) {
+ const userData = await fetchUserData(userId)
+
+ if (skipProcessing) {
+ // Returns immediately but still waited for userData
+ return { skipped: true }
+ }
+
+ // Only this branch uses userData
+ return processUserData(userData)
+}
+```
+
+**Correct (only blocks when needed):**
+
+```typescript
+async function handleRequest(userId: string, skipProcessing: boolean) {
+ if (skipProcessing) {
+ // Returns immediately without waiting
+ return { skipped: true }
+ }
+
+ // Fetch only when needed
+ const userData = await fetchUserData(userId)
+ return processUserData(userData)
+}
+```
+
+**Another example (early return optimization):**
+
+```typescript
+// Incorrect: always fetches permissions
+async function updateResource(resourceId: string, userId: string) {
+ const permissions = await fetchPermissions(userId)
+ const resource = await getResource(resourceId)
+
+ if (!resource) {
+ return { error: 'Not found' }
+ }
+
+ if (!permissions.canEdit) {
+ return { error: 'Forbidden' }
+ }
+
+ return await updateResourceData(resource, permissions)
+}
+
+// Correct: fetches only when needed
+async function updateResource(resourceId: string, userId: string) {
+ const resource = await getResource(resourceId)
+
+ if (!resource) {
+ return { error: 'Not found' }
+ }
+
+ const permissions = await fetchPermissions(userId)
+
+ if (!permissions.canEdit) {
+ return { error: 'Forbidden' }
+ }
+
+ return await updateResourceData(resource, permissions)
+}
+```
+
+This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/async-dependencies.md b/.cursor/skills/vercel-react-best-practices/rules/async-dependencies.md
new file mode 100644
index 0000000..06b54c7
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/async-dependencies.md
@@ -0,0 +1,37 @@
+---
+title: Dependency-Based Parallelization
+impact: CRITICAL
+impactDescription: 2-10× improvement
+tags: async, parallelization, dependencies, better-all
+---
+
+## Dependency-Based Parallelization
+
+For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.
+
+**Incorrect (profile waits for config unnecessarily):**
+
+```typescript
+const [user, config] = await Promise.all([fetchUser(), fetchConfig()])
+const profile = await fetchProfile(user.id)
+```
+
+**Correct (config and profile run in parallel):**
+
+```typescript
+import { all } from 'better-all'
+
+const { user, config, profile } = await all({
+ async user() {
+ return fetchUser()
+ },
+ async config() {
+ return fetchConfig()
+ },
+ async profile() {
+ return fetchProfile((await this.$.user).id)
+ },
+})
+```
+
+Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)
diff --git a/.cursor/skills/vercel-react-best-practices/rules/async-parallel.md b/.cursor/skills/vercel-react-best-practices/rules/async-parallel.md
new file mode 100644
index 0000000..4b9c233
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/async-parallel.md
@@ -0,0 +1,24 @@
+---
+title: Promise.all() for Independent Operations
+impact: CRITICAL
+impactDescription: 2-10× improvement
+tags: async, parallelization, promises, waterfalls
+---
+
+## Promise.all() for Independent Operations
+
+When async operations have no interdependencies, execute them concurrently using `Promise.all()`.
+
+**Incorrect (sequential execution, 3 round trips):**
+
+```typescript
+const user = await fetchUser()
+const posts = await fetchPosts()
+const comments = await fetchComments()
+```
+
+**Correct (parallel execution, 1 round trip):**
+
+```typescript
+const [user, posts, comments] = await Promise.all([fetchUser(), fetchPosts(), fetchComments()])
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md b/.cursor/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md
new file mode 100644
index 0000000..b12794c
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md
@@ -0,0 +1,99 @@
+---
+title: Strategic Suspense Boundaries
+impact: HIGH
+impactDescription: faster initial paint
+tags: async, suspense, streaming, layout-shift
+---
+
+## Strategic Suspense Boundaries
+
+Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.
+
+**Incorrect (wrapper blocked by data fetching):**
+
+```tsx
+async function Page() {
+ const data = await fetchData() // Blocks entire page
+
+ return (
+
+
Sidebar
+
Header
+
+
+
+
Footer
+
+ )
+}
+```
+
+The entire layout waits for data even though only the middle section needs it.
+
+**Correct (wrapper shows immediately, data streams in):**
+
+```tsx
+function Page() {
+ return (
+
+
Sidebar
+
Header
+
+ }>
+
+
+
+
Footer
+
+ )
+}
+
+async function DataDisplay() {
+ const data = await fetchData() // Only blocks this component
+ return {data.content}
+}
+```
+
+Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data.
+
+**Alternative (share promise across components):**
+
+```tsx
+function Page() {
+ // Start fetch immediately, but don't await
+ const dataPromise = fetchData()
+
+ return (
+
+
Sidebar
+
Header
+
}>
+
+
+
+
Footer
+
+ )
+}
+
+function DataDisplay({ dataPromise }: { dataPromise: Promise }) {
+ const data = use(dataPromise) // Unwraps the promise
+ return {data.content}
+}
+
+function DataSummary({ dataPromise }: { dataPromise: Promise }) {
+ const data = use(dataPromise) // Reuses the same promise
+ return {data.summary}
+}
+```
+
+Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.
+
+**When NOT to use this pattern:**
+
+- Critical data needed for layout decisions (affects positioning)
+- SEO-critical content above the fold
+- Small, fast queries where suspense overhead isn't worth it
+- When you want to avoid layout shift (loading → content jump)
+
+**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md b/.cursor/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md
new file mode 100644
index 0000000..5012655
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md
@@ -0,0 +1,59 @@
+---
+title: Avoid Barrel File Imports
+impact: CRITICAL
+impactDescription: 200-800ms import cost, slow builds
+tags: bundle, imports, tree-shaking, barrel-files, performance
+---
+
+## Avoid Barrel File Imports
+
+Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).
+
+Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.
+
+**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.
+
+**Incorrect (imports entire library):**
+
+```tsx
+import { Check, X, Menu } from 'lucide-react'
+// Loads 1,583 modules, takes ~2.8s extra in dev
+// Runtime cost: 200-800ms on every cold start
+
+import { Button, TextField } from '@mui/material'
+// Loads 2,225 modules, takes ~4.2s extra in dev
+```
+
+**Correct (imports only what you need):**
+
+```tsx
+import Check from 'lucide-react/dist/esm/icons/check'
+import X from 'lucide-react/dist/esm/icons/x'
+import Menu from 'lucide-react/dist/esm/icons/menu'
+// Loads only 3 modules (~2KB vs ~1MB)
+
+import Button from '@mui/material/Button'
+import TextField from '@mui/material/TextField'
+// Loads only what you use
+```
+
+**Alternative (Next.js 13.5+):**
+
+```js
+// next.config.js - use optimizePackageImports
+module.exports = {
+ experimental: {
+ optimizePackageImports: ['lucide-react', '@mui/material'],
+ },
+}
+
+// Then you can keep the ergonomic barrel imports:
+import { Check, X, Menu } from 'lucide-react'
+// Automatically transformed to direct imports at build time
+```
+
+Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.
+
+Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.
+
+Reference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
diff --git a/.cursor/skills/vercel-react-best-practices/rules/bundle-conditional.md b/.cursor/skills/vercel-react-best-practices/rules/bundle-conditional.md
new file mode 100644
index 0000000..40bd6f9
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/bundle-conditional.md
@@ -0,0 +1,31 @@
+---
+title: Conditional Module Loading
+impact: HIGH
+impactDescription: loads large data only when needed
+tags: bundle, conditional-loading, lazy-loading
+---
+
+## Conditional Module Loading
+
+Load large data or modules only when a feature is activated.
+
+**Example (lazy-load animation frames):**
+
+```tsx
+function AnimationPlayer({ enabled }: { enabled: boolean }) {
+ const [frames, setFrames] = useState (null)
+
+ useEffect(() => {
+ if (enabled && !frames && typeof window !== 'undefined') {
+ import('./animation-frames.js')
+ .then(mod => setFrames(mod.frames))
+ .catch(() => setEnabled(false))
+ }
+ }, [enabled, frames])
+
+ if (!frames) return
+ return
+}
+```
+
+The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md b/.cursor/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md
new file mode 100644
index 0000000..3c89cba
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md
@@ -0,0 +1,48 @@
+---
+title: Defer Non-Critical Third-Party Libraries
+impact: MEDIUM
+impactDescription: loads after hydration
+tags: bundle, third-party, analytics, defer
+---
+
+## Defer Non-Critical Third-Party Libraries
+
+Analytics, logging, and error tracking don't block user interaction. Load them after hydration.
+
+**Incorrect (blocks initial bundle):**
+
+```tsx
+import { Analytics } from '@vercel/analytics/react'
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+```
+
+**Correct (loads after hydration):**
+
+```tsx
+import dynamic from 'next/dynamic'
+
+const Analytics = dynamic(() => import('@vercel/analytics/react').then(m => m.Analytics), {
+ ssr: false,
+})
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md b/.cursor/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md
new file mode 100644
index 0000000..14e8229
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md
@@ -0,0 +1,34 @@
+---
+title: Dynamic Imports for Heavy Components
+impact: CRITICAL
+impactDescription: directly affects TTI and LCP
+tags: bundle, dynamic-import, code-splitting, next-dynamic
+---
+
+## Dynamic Imports for Heavy Components
+
+Use `next/dynamic` to lazy-load large components not needed on initial render.
+
+**Incorrect (Monaco bundles with main chunk ~300KB):**
+
+```tsx
+import { MonacoEditor } from './monaco-editor'
+
+function CodePanel({ code }: { code: string }) {
+ return
+}
+```
+
+**Correct (Monaco loads on demand):**
+
+```tsx
+import dynamic from 'next/dynamic'
+
+const MonacoEditor = dynamic(() => import('./monaco-editor').then(m => m.MonacoEditor), {
+ ssr: false,
+})
+
+function CodePanel({ code }: { code: string }) {
+ return
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/bundle-preload.md b/.cursor/skills/vercel-react-best-practices/rules/bundle-preload.md
new file mode 100644
index 0000000..2f1c9b9
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/bundle-preload.md
@@ -0,0 +1,44 @@
+---
+title: Preload Based on User Intent
+impact: MEDIUM
+impactDescription: reduces perceived latency
+tags: bundle, preload, user-intent, hover
+---
+
+## Preload Based on User Intent
+
+Preload heavy bundles before they're needed to reduce perceived latency.
+
+**Example (preload on hover/focus):**
+
+```tsx
+function EditorButton({ onClick }: { onClick: () => void }) {
+ const preload = () => {
+ if (typeof window !== 'undefined') {
+ void import('./monaco-editor')
+ }
+ }
+
+ return (
+
+ Open Editor
+
+ )
+}
+```
+
+**Example (preload when feature flag is enabled):**
+
+```tsx
+function FlagsProvider({ children, flags }: Props) {
+ useEffect(() => {
+ if (flags.editorEnabled && typeof window !== 'undefined') {
+ void import('./monaco-editor').then(mod => mod.init())
+ }
+ }, [flags.editorEnabled])
+
+ return {children}
+}
+```
+
+The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/client-event-listeners.md b/.cursor/skills/vercel-react-best-practices/rules/client-event-listeners.md
new file mode 100644
index 0000000..496c359
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/client-event-listeners.md
@@ -0,0 +1,78 @@
+---
+title: Deduplicate Global Event Listeners
+impact: LOW
+impactDescription: single listener for N components
+tags: client, swr, event-listeners, subscription
+---
+
+## Deduplicate Global Event Listeners
+
+Use `useSWRSubscription()` to share global event listeners across component instances.
+
+**Incorrect (N instances = N listeners):**
+
+```tsx
+function useKeyboardShortcut(key: string, callback: () => void) {
+ useEffect(() => {
+ const handler = (e: KeyboardEvent) => {
+ if (e.metaKey && e.key === key) {
+ callback()
+ }
+ }
+ window.addEventListener('keydown', handler)
+ return () => window.removeEventListener('keydown', handler)
+ }, [key, callback])
+}
+```
+
+When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.
+
+**Correct (N instances = 1 listener):**
+
+```tsx
+import useSWRSubscription from 'swr/subscription'
+
+// Module-level Map to track callbacks per key
+const keyCallbacks = new Map void>>()
+
+function useKeyboardShortcut(key: string, callback: () => void) {
+ // Register this callback in the Map
+ useEffect(() => {
+ if (!keyCallbacks.has(key)) {
+ keyCallbacks.set(key, new Set())
+ }
+ keyCallbacks.get(key)!.add(callback)
+
+ return () => {
+ const set = keyCallbacks.get(key)
+ if (set) {
+ set.delete(callback)
+ if (set.size === 0) {
+ keyCallbacks.delete(key)
+ }
+ }
+ }
+ }, [key, callback])
+
+ useSWRSubscription('global-keydown', () => {
+ const handler = (e: KeyboardEvent) => {
+ if (e.metaKey && keyCallbacks.has(e.key)) {
+ keyCallbacks.get(e.key)!.forEach(cb => cb())
+ }
+ }
+ window.addEventListener('keydown', handler)
+ return () => window.removeEventListener('keydown', handler)
+ })
+}
+
+function Profile() {
+ // Multiple shortcuts will share the same listener
+ useKeyboardShortcut('p', () => {
+ /* ... */
+ })
+ useKeyboardShortcut('k', () => {
+ /* ... */
+ })
+ // ...
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/client-swr-dedup.md b/.cursor/skills/vercel-react-best-practices/rules/client-swr-dedup.md
new file mode 100644
index 0000000..2a430f2
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/client-swr-dedup.md
@@ -0,0 +1,56 @@
+---
+title: Use SWR for Automatic Deduplication
+impact: MEDIUM-HIGH
+impactDescription: automatic deduplication
+tags: client, swr, deduplication, data-fetching
+---
+
+## Use SWR for Automatic Deduplication
+
+SWR enables request deduplication, caching, and revalidation across component instances.
+
+**Incorrect (no deduplication, each instance fetches):**
+
+```tsx
+function UserList() {
+ const [users, setUsers] = useState([])
+ useEffect(() => {
+ fetch('/api/users')
+ .then(r => r.json())
+ .then(setUsers)
+ }, [])
+}
+```
+
+**Correct (multiple instances share one request):**
+
+```tsx
+import useSWR from 'swr'
+
+function UserList() {
+ const { data: users } = useSWR('/api/users', fetcher)
+}
+```
+
+**For immutable data:**
+
+```tsx
+import { useImmutableSWR } from '@/lib/swr'
+
+function StaticContent() {
+ const { data } = useImmutableSWR('/api/config', fetcher)
+}
+```
+
+**For mutations:**
+
+```tsx
+import { useSWRMutation } from 'swr/mutation'
+
+function UpdateButton() {
+ const { trigger } = useSWRMutation('/api/user', updateUser)
+ return trigger()}>Update
+}
+```
+
+Reference: [https://swr.vercel.app](https://swr.vercel.app)
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-batch-dom-css.md b/.cursor/skills/vercel-react-best-practices/rules/js-batch-dom-css.md
new file mode 100644
index 0000000..c2a7f8d
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-batch-dom-css.md
@@ -0,0 +1,78 @@
+---
+title: Batch DOM CSS Changes
+impact: MEDIUM
+impactDescription: reduces reflows/repaints
+tags: javascript, dom, css, performance, reflow
+---
+
+## Batch DOM CSS Changes
+
+Avoid changing styles one property at a time. Group multiple CSS changes together via classes or `cssText` to minimize browser reflows.
+
+**Incorrect (multiple reflows):**
+
+```typescript
+function updateElementStyles(element: HTMLElement) {
+ // Each line triggers a reflow
+ element.style.width = '100px'
+ element.style.height = '200px'
+ element.style.backgroundColor = 'blue'
+ element.style.border = '1px solid black'
+}
+```
+
+**Correct (add class - single reflow):**
+
+```typescript
+// CSS file
+.highlighted-box {
+ width: 100px;
+ height: 200px;
+ background-color: blue;
+ border: 1px solid black;
+}
+
+// JavaScript
+function updateElementStyles(element: HTMLElement) {
+ element.classList.add('highlighted-box')
+}
+```
+
+**Correct (change cssText - single reflow):**
+
+```typescript
+function updateElementStyles(element: HTMLElement) {
+ element.style.cssText = `
+ width: 100px;
+ height: 200px;
+ background-color: blue;
+ border: 1px solid black;
+ `
+}
+```
+
+**React example:**
+
+```tsx
+// Incorrect: changing styles one by one
+function Box({ isHighlighted }: { isHighlighted: boolean }) {
+ const ref = useRef(null)
+
+ useEffect(() => {
+ if (ref.current && isHighlighted) {
+ ref.current.style.width = '100px'
+ ref.current.style.height = '200px'
+ ref.current.style.backgroundColor = 'blue'
+ }
+ }, [isHighlighted])
+
+ return Content
+}
+
+// Correct: toggle class
+function Box({ isHighlighted }: { isHighlighted: boolean }) {
+ return Content
+}
+```
+
+Prefer CSS classes over inline styles when possible. Classes are cached by the browser and provide better separation of concerns.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-cache-function-results.md b/.cursor/skills/vercel-react-best-practices/rules/js-cache-function-results.md
new file mode 100644
index 0000000..7b986cf
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-cache-function-results.md
@@ -0,0 +1,80 @@
+---
+title: Cache Repeated Function Calls
+impact: MEDIUM
+impactDescription: avoid redundant computation
+tags: javascript, cache, memoization, performance
+---
+
+## Cache Repeated Function Calls
+
+Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.
+
+**Incorrect (redundant computation):**
+
+```typescript
+function ProjectList({ projects }: { projects: Project[] }) {
+ return (
+
+ {projects.map(project => {
+ // slugify() called 100+ times for same project names
+ const slug = slugify(project.name)
+
+ return
+ })}
+
+ )
+}
+```
+
+**Correct (cached results):**
+
+```typescript
+// Module-level cache
+const slugifyCache = new Map()
+
+function cachedSlugify(text: string): string {
+ if (slugifyCache.has(text)) {
+ return slugifyCache.get(text)!
+ }
+ const result = slugify(text)
+ slugifyCache.set(text, result)
+ return result
+}
+
+function ProjectList({ projects }: { projects: Project[] }) {
+ return (
+
+ {projects.map(project => {
+ // Computed only once per unique project name
+ const slug = cachedSlugify(project.name)
+
+ return
+ })}
+
+ )
+}
+```
+
+**Simpler pattern for single-value functions:**
+
+```typescript
+let isLoggedInCache: boolean | null = null
+
+function isLoggedIn(): boolean {
+ if (isLoggedInCache !== null) {
+ return isLoggedInCache
+ }
+
+ isLoggedInCache = document.cookie.includes('auth=')
+ return isLoggedInCache
+}
+
+// Clear cache when auth changes
+function onAuthChange() {
+ isLoggedInCache = null
+}
+```
+
+Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
+
+Reference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-cache-property-access.md b/.cursor/skills/vercel-react-best-practices/rules/js-cache-property-access.md
new file mode 100644
index 0000000..39eec90
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-cache-property-access.md
@@ -0,0 +1,28 @@
+---
+title: Cache Property Access in Loops
+impact: LOW-MEDIUM
+impactDescription: reduces lookups
+tags: javascript, loops, optimization, caching
+---
+
+## Cache Property Access in Loops
+
+Cache object property lookups in hot paths.
+
+**Incorrect (3 lookups × N iterations):**
+
+```typescript
+for (let i = 0; i < arr.length; i++) {
+ process(obj.config.settings.value)
+}
+```
+
+**Correct (1 lookup total):**
+
+```typescript
+const value = obj.config.settings.value
+const len = arr.length
+for (let i = 0; i < len; i++) {
+ process(value)
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-cache-storage.md b/.cursor/skills/vercel-react-best-practices/rules/js-cache-storage.md
new file mode 100644
index 0000000..9fa6201
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-cache-storage.md
@@ -0,0 +1,68 @@
+---
+title: Cache Storage API Calls
+impact: LOW-MEDIUM
+impactDescription: reduces expensive I/O
+tags: javascript, localStorage, storage, caching, performance
+---
+
+## Cache Storage API Calls
+
+`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.
+
+**Incorrect (reads storage on every call):**
+
+```typescript
+function getTheme() {
+ return localStorage.getItem('theme') ?? 'light'
+}
+// Called 10 times = 10 storage reads
+```
+
+**Correct (Map cache):**
+
+```typescript
+const storageCache = new Map()
+
+function getLocalStorage(key: string) {
+ if (!storageCache.has(key)) {
+ storageCache.set(key, localStorage.getItem(key))
+ }
+ return storageCache.get(key)
+}
+
+function setLocalStorage(key: string, value: string) {
+ localStorage.setItem(key, value)
+ storageCache.set(key, value) // keep cache in sync
+}
+```
+
+Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
+
+**Cookie caching:**
+
+```typescript
+let cookieCache: Record | null = null
+
+function getCookie(name: string) {
+ if (!cookieCache) {
+ cookieCache = Object.fromEntries(document.cookie.split('; ').map(c => c.split('=')))
+ }
+ return cookieCache[name]
+}
+```
+
+**Important (invalidate on external changes):**
+
+If storage can change externally (another tab, server-set cookies), invalidate cache:
+
+```typescript
+window.addEventListener('storage', e => {
+ if (e.key) storageCache.delete(e.key)
+})
+
+document.addEventListener('visibilitychange', () => {
+ if (document.visibilityState === 'visible') {
+ storageCache.clear()
+ }
+})
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-combine-iterations.md b/.cursor/skills/vercel-react-best-practices/rules/js-combine-iterations.md
new file mode 100644
index 0000000..044d017
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-combine-iterations.md
@@ -0,0 +1,32 @@
+---
+title: Combine Multiple Array Iterations
+impact: LOW-MEDIUM
+impactDescription: reduces iterations
+tags: javascript, arrays, loops, performance
+---
+
+## Combine Multiple Array Iterations
+
+Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
+
+**Incorrect (3 iterations):**
+
+```typescript
+const admins = users.filter(u => u.isAdmin)
+const testers = users.filter(u => u.isTester)
+const inactive = users.filter(u => !u.isActive)
+```
+
+**Correct (1 iteration):**
+
+```typescript
+const admins: User[] = []
+const testers: User[] = []
+const inactive: User[] = []
+
+for (const user of users) {
+ if (user.isAdmin) admins.push(user)
+ if (user.isTester) testers.push(user)
+ if (!user.isActive) inactive.push(user)
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-early-exit.md b/.cursor/skills/vercel-react-best-practices/rules/js-early-exit.md
new file mode 100644
index 0000000..44a8c58
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-early-exit.md
@@ -0,0 +1,50 @@
+---
+title: Early Return from Functions
+impact: LOW-MEDIUM
+impactDescription: avoids unnecessary computation
+tags: javascript, functions, optimization, early-return
+---
+
+## Early Return from Functions
+
+Return early when result is determined to skip unnecessary processing.
+
+**Incorrect (processes all items even after finding answer):**
+
+```typescript
+function validateUsers(users: User[]) {
+ let hasError = false
+ let errorMessage = ''
+
+ for (const user of users) {
+ if (!user.email) {
+ hasError = true
+ errorMessage = 'Email required'
+ }
+ if (!user.name) {
+ hasError = true
+ errorMessage = 'Name required'
+ }
+ // Continues checking all users even after error found
+ }
+
+ return hasError ? { valid: false, error: errorMessage } : { valid: true }
+}
+```
+
+**Correct (returns immediately on first error):**
+
+```typescript
+function validateUsers(users: User[]) {
+ for (const user of users) {
+ if (!user.email) {
+ return { valid: false, error: 'Email required' }
+ }
+ if (!user.name) {
+ return { valid: false, error: 'Name required' }
+ }
+ }
+
+ return { valid: true }
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-hoist-regexp.md b/.cursor/skills/vercel-react-best-practices/rules/js-hoist-regexp.md
new file mode 100644
index 0000000..4efb5c3
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-hoist-regexp.md
@@ -0,0 +1,45 @@
+---
+title: Hoist RegExp Creation
+impact: LOW-MEDIUM
+impactDescription: avoids recreation
+tags: javascript, regexp, optimization, memoization
+---
+
+## Hoist RegExp Creation
+
+Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.
+
+**Incorrect (new RegExp every render):**
+
+```tsx
+function Highlighter({ text, query }: Props) {
+ const regex = new RegExp(`(${query})`, 'gi')
+ const parts = text.split(regex)
+ return <>{parts.map((part, i) => ...)}>
+}
+```
+
+**Correct (memoize or hoist):**
+
+```tsx
+const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+
+function Highlighter({ text, query }: Props) {
+ const regex = useMemo(
+ () => new RegExp(`(${escapeRegex(query)})`, 'gi'),
+ [query]
+ )
+ const parts = text.split(regex)
+ return <>{parts.map((part, i) => ...)}>
+}
+```
+
+**Warning (global regex has mutable state):**
+
+Global regex (`/g`) has mutable `lastIndex` state:
+
+```typescript
+const regex = /foo/g
+regex.test('foo') // true, lastIndex = 3
+regex.test('foo') // false, lastIndex = 0
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-index-maps.md b/.cursor/skills/vercel-react-best-practices/rules/js-index-maps.md
new file mode 100644
index 0000000..ae1951b
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-index-maps.md
@@ -0,0 +1,37 @@
+---
+title: Build Index Maps for Repeated Lookups
+impact: LOW-MEDIUM
+impactDescription: 1M ops to 2K ops
+tags: javascript, map, indexing, optimization, performance
+---
+
+## Build Index Maps for Repeated Lookups
+
+Multiple `.find()` calls by the same key should use a Map.
+
+**Incorrect (O(n) per lookup):**
+
+```typescript
+function processOrders(orders: Order[], users: User[]) {
+ return orders.map(order => ({
+ ...order,
+ user: users.find(u => u.id === order.userId),
+ }))
+}
+```
+
+**Correct (O(1) per lookup):**
+
+```typescript
+function processOrders(orders: Order[], users: User[]) {
+ const userById = new Map(users.map(u => [u.id, u]))
+
+ return orders.map(order => ({
+ ...order,
+ user: userById.get(order.userId),
+ }))
+}
+```
+
+Build map once (O(n)), then all lookups are O(1).
+For 1000 orders × 1000 users: 1M ops → 2K ops.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-length-check-first.md b/.cursor/skills/vercel-react-best-practices/rules/js-length-check-first.md
new file mode 100644
index 0000000..3abd2e8
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-length-check-first.md
@@ -0,0 +1,50 @@
+---
+title: Early Length Check for Array Comparisons
+impact: MEDIUM-HIGH
+impactDescription: avoids expensive operations when lengths differ
+tags: javascript, arrays, performance, optimization, comparison
+---
+
+## Early Length Check for Array Comparisons
+
+When comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.
+
+In real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).
+
+**Incorrect (always runs expensive comparison):**
+
+```typescript
+function hasChanges(current: string[], original: string[]) {
+ // Always sorts and joins, even when lengths differ
+ return current.sort().join() !== original.sort().join()
+}
+```
+
+Two O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.
+
+**Correct (O(1) length check first):**
+
+```typescript
+function hasChanges(current: string[], original: string[]) {
+ // Early return if lengths differ
+ if (current.length !== original.length) {
+ return true
+ }
+ // Only sort/join when lengths match
+ const currentSorted = current.toSorted()
+ const originalSorted = original.toSorted()
+ for (let i = 0; i < currentSorted.length; i++) {
+ if (currentSorted[i] !== originalSorted[i]) {
+ return true
+ }
+ }
+ return false
+}
+```
+
+This new approach is more efficient because:
+
+- It avoids the overhead of sorting and joining the arrays when lengths differ
+- It avoids consuming memory for the joined strings (especially important for large arrays)
+- It avoids mutating the original arrays
+- It returns early when a difference is found
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-min-max-loop.md b/.cursor/skills/vercel-react-best-practices/rules/js-min-max-loop.md
new file mode 100644
index 0000000..8cf7075
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-min-max-loop.md
@@ -0,0 +1,82 @@
+---
+title: Use Loop for Min/Max Instead of Sort
+impact: LOW
+impactDescription: O(n) instead of O(n log n)
+tags: javascript, arrays, performance, sorting, algorithms
+---
+
+## Use Loop for Min/Max Instead of Sort
+
+Finding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.
+
+**Incorrect (O(n log n) - sort to find latest):**
+
+```typescript
+interface Project {
+ id: string
+ name: string
+ updatedAt: number
+}
+
+function getLatestProject(projects: Project[]) {
+ const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)
+ return sorted[0]
+}
+```
+
+Sorts the entire array just to find the maximum value.
+
+**Incorrect (O(n log n) - sort for oldest and newest):**
+
+```typescript
+function getOldestAndNewest(projects: Project[]) {
+ const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)
+ return { oldest: sorted[0], newest: sorted[sorted.length - 1] }
+}
+```
+
+Still sorts unnecessarily when only min/max are needed.
+
+**Correct (O(n) - single loop):**
+
+```typescript
+function getLatestProject(projects: Project[]) {
+ if (projects.length === 0) return null
+
+ let latest = projects[0]
+
+ for (let i = 1; i < projects.length; i++) {
+ if (projects[i].updatedAt > latest.updatedAt) {
+ latest = projects[i]
+ }
+ }
+
+ return latest
+}
+
+function getOldestAndNewest(projects: Project[]) {
+ if (projects.length === 0) return { oldest: null, newest: null }
+
+ let oldest = projects[0]
+ let newest = projects[0]
+
+ for (let i = 1; i < projects.length; i++) {
+ if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]
+ if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]
+ }
+
+ return { oldest, newest }
+}
+```
+
+Single pass through the array, no copying, no sorting.
+
+**Alternative (Math.min/Math.max for small arrays):**
+
+```typescript
+const numbers = [5, 2, 8, 1, 9]
+const min = Math.min(...numbers)
+const max = Math.max(...numbers)
+```
+
+This works for small arrays but can be slower for very large arrays due to spread operator limitations. Use the loop approach for reliability.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-set-map-lookups.md b/.cursor/skills/vercel-react-best-practices/rules/js-set-map-lookups.md
new file mode 100644
index 0000000..680a489
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-set-map-lookups.md
@@ -0,0 +1,24 @@
+---
+title: Use Set/Map for O(1) Lookups
+impact: LOW-MEDIUM
+impactDescription: O(n) to O(1)
+tags: javascript, set, map, data-structures, performance
+---
+
+## Use Set/Map for O(1) Lookups
+
+Convert arrays to Set/Map for repeated membership checks.
+
+**Incorrect (O(n) per check):**
+
+```typescript
+const allowedIds = ['a', 'b', 'c', ...]
+items.filter(item => allowedIds.includes(item.id))
+```
+
+**Correct (O(1) per check):**
+
+```typescript
+const allowedIds = new Set(['a', 'b', 'c', ...])
+items.filter(item => allowedIds.has(item.id))
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md b/.cursor/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md
new file mode 100644
index 0000000..eae8b3f
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md
@@ -0,0 +1,57 @@
+---
+title: Use toSorted() Instead of sort() for Immutability
+impact: MEDIUM-HIGH
+impactDescription: prevents mutation bugs in React state
+tags: javascript, arrays, immutability, react, state, mutation
+---
+
+## Use toSorted() Instead of sort() for Immutability
+
+`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation.
+
+**Incorrect (mutates original array):**
+
+```typescript
+function UserList({ users }: { users: User[] }) {
+ // Mutates the users prop array!
+ const sorted = useMemo(
+ () => users.sort((a, b) => a.name.localeCompare(b.name)),
+ [users]
+ )
+ return {sorted.map(renderUser)}
+}
+```
+
+**Correct (creates new array):**
+
+```typescript
+function UserList({ users }: { users: User[] }) {
+ // Creates new sorted array, original unchanged
+ const sorted = useMemo(
+ () => users.toSorted((a, b) => a.name.localeCompare(b.name)),
+ [users]
+ )
+ return {sorted.map(renderUser)}
+}
+```
+
+**Why this matters in React:**
+
+1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only
+2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior
+
+**Browser support (fallback for older browsers):**
+
+`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:
+
+```typescript
+// Fallback for older browsers
+const sorted = [...items].sort((a, b) => a.value - b.value)
+```
+
+**Other immutable array methods:**
+
+- `.toSorted()` - immutable sort
+- `.toReversed()` - immutable reverse
+- `.toSpliced()` - immutable splice
+- `.with()` - immutable element replacement
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rendering-activity.md b/.cursor/skills/vercel-react-best-practices/rules/rendering-activity.md
new file mode 100644
index 0000000..c957a49
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rendering-activity.md
@@ -0,0 +1,26 @@
+---
+title: Use Activity Component for Show/Hide
+impact: MEDIUM
+impactDescription: preserves state/DOM
+tags: rendering, activity, visibility, state-preservation
+---
+
+## Use Activity Component for Show/Hide
+
+Use React's `` to preserve state/DOM for expensive components that frequently toggle visibility.
+
+**Usage:**
+
+```tsx
+import { Activity } from 'react'
+
+function Dropdown({ isOpen }: Props) {
+ return (
+
+
+
+ )
+}
+```
+
+Avoids expensive re-renders and state loss.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md b/.cursor/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md
new file mode 100644
index 0000000..70935b8
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md
@@ -0,0 +1,38 @@
+---
+title: Animate SVG Wrapper Instead of SVG Element
+impact: LOW
+impactDescription: enables hardware acceleration
+tags: rendering, svg, css, animation, performance
+---
+
+## Animate SVG Wrapper Instead of SVG Element
+
+Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `` and animate the wrapper instead.
+
+**Incorrect (animating SVG directly - no hardware acceleration):**
+
+```tsx
+function LoadingSpinner() {
+ return (
+
+
+
+ )
+}
+```
+
+**Correct (animating wrapper div - hardware accelerated):**
+
+```tsx
+function LoadingSpinner() {
+ return (
+
+
+
+
+
+ )
+}
+```
+
+This applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rendering-conditional-render.md b/.cursor/skills/vercel-react-best-practices/rules/rendering-conditional-render.md
new file mode 100644
index 0000000..eb16288
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rendering-conditional-render.md
@@ -0,0 +1,32 @@
+---
+title: Use Explicit Conditional Rendering
+impact: LOW
+impactDescription: prevents rendering 0 or NaN
+tags: rendering, conditional, jsx, falsy-values
+---
+
+## Use Explicit Conditional Rendering
+
+Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.
+
+**Incorrect (renders "0" when count is 0):**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return
{count && {count} }
+}
+
+// When count = 0, renders:
0
+// When count = 5, renders:
5
+```
+
+**Correct (renders nothing when count is 0):**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return
{count > 0 ? {count} : null}
+}
+
+// When count = 0, renders:
+// When count = 5, renders:
5
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rendering-content-visibility.md b/.cursor/skills/vercel-react-best-practices/rules/rendering-content-visibility.md
new file mode 100644
index 0000000..aa66563
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rendering-content-visibility.md
@@ -0,0 +1,38 @@
+---
+title: CSS content-visibility for Long Lists
+impact: HIGH
+impactDescription: faster initial render
+tags: rendering, css, content-visibility, long-lists
+---
+
+## CSS content-visibility for Long Lists
+
+Apply `content-visibility: auto` to defer off-screen rendering.
+
+**CSS:**
+
+```css
+.message-item {
+ content-visibility: auto;
+ contain-intrinsic-size: 0 80px;
+}
+```
+
+**Example:**
+
+```tsx
+function MessageList({ messages }: { messages: Message[] }) {
+ return (
+
+ {messages.map(msg => (
+
+ ))}
+
+ )
+}
+```
+
+For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md b/.cursor/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md
new file mode 100644
index 0000000..05a4dda
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md
@@ -0,0 +1,36 @@
+---
+title: Hoist Static JSX Elements
+impact: LOW
+impactDescription: avoids re-creation
+tags: rendering, jsx, static, optimization
+---
+
+## Hoist Static JSX Elements
+
+Extract static JSX outside components to avoid re-creation.
+
+**Incorrect (recreates element every render):**
+
+```tsx
+function LoadingSkeleton() {
+ return
+}
+
+function Container() {
+ return
{loading && }
+}
+```
+
+**Correct (reuses same element):**
+
+```tsx
+const loadingSkeleton =
+
+function Container() {
+ return
{loading && loadingSkeleton}
+}
+```
+
+This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md b/.cursor/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md
new file mode 100644
index 0000000..4a9ec7a
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md
@@ -0,0 +1,72 @@
+---
+title: Prevent Hydration Mismatch Without Flickering
+impact: MEDIUM
+impactDescription: avoids visual flicker and hydration errors
+tags: rendering, ssr, hydration, localStorage, flicker
+---
+
+## Prevent Hydration Mismatch Without Flickering
+
+When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.
+
+**Incorrect (breaks SSR):**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ // localStorage is not available on server - throws error
+ const theme = localStorage.getItem('theme') || 'light'
+
+ return
{children}
+}
+```
+
+Server-side rendering will fail because `localStorage` is undefined.
+
+**Incorrect (visual flickering):**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ const [theme, setTheme] = useState('light')
+
+ useEffect(() => {
+ // Runs after hydration - causes visible flash
+ const stored = localStorage.getItem('theme')
+ if (stored) {
+ setTheme(stored)
+ }
+ }, [])
+
+ return
{children}
+}
+```
+
+Component first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content.
+
+**Correct (no flicker, no hydration mismatch):**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ return (
+ <>
+
{children}
+
+ >
+ )
+}
+```
+
+The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.
+
+This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rendering-svg-precision.md b/.cursor/skills/vercel-react-best-practices/rules/rendering-svg-precision.md
new file mode 100644
index 0000000..6d77128
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rendering-svg-precision.md
@@ -0,0 +1,28 @@
+---
+title: Optimize SVG Precision
+impact: LOW
+impactDescription: reduces file size
+tags: rendering, svg, optimization, svgo
+---
+
+## Optimize SVG Precision
+
+Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.
+
+**Incorrect (excessive precision):**
+
+```svg
+
+```
+
+**Correct (1 decimal place):**
+
+```svg
+
+```
+
+**Automate with SVGO:**
+
+```bash
+npx svgo --precision=1 --multipass icon.svg
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rerender-defer-reads.md b/.cursor/skills/vercel-react-best-practices/rules/rerender-defer-reads.md
new file mode 100644
index 0000000..e867c95
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rerender-defer-reads.md
@@ -0,0 +1,39 @@
+---
+title: Defer State Reads to Usage Point
+impact: MEDIUM
+impactDescription: avoids unnecessary subscriptions
+tags: rerender, searchParams, localStorage, optimization
+---
+
+## Defer State Reads to Usage Point
+
+Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.
+
+**Incorrect (subscribes to all searchParams changes):**
+
+```tsx
+function ShareButton({ chatId }: { chatId: string }) {
+ const searchParams = useSearchParams()
+
+ const handleShare = () => {
+ const ref = searchParams.get('ref')
+ shareChat(chatId, { ref })
+ }
+
+ return
Share
+}
+```
+
+**Correct (reads on demand, no subscription):**
+
+```tsx
+function ShareButton({ chatId }: { chatId: string }) {
+ const handleShare = () => {
+ const params = new URLSearchParams(window.location.search)
+ const ref = params.get('ref')
+ shareChat(chatId, { ref })
+ }
+
+ return
Share
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rerender-dependencies.md b/.cursor/skills/vercel-react-best-practices/rules/rerender-dependencies.md
new file mode 100644
index 0000000..47a4d92
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rerender-dependencies.md
@@ -0,0 +1,45 @@
+---
+title: Narrow Effect Dependencies
+impact: LOW
+impactDescription: minimizes effect re-runs
+tags: rerender, useEffect, dependencies, optimization
+---
+
+## Narrow Effect Dependencies
+
+Specify primitive dependencies instead of objects to minimize effect re-runs.
+
+**Incorrect (re-runs on any user field change):**
+
+```tsx
+useEffect(() => {
+ console.log(user.id)
+}, [user])
+```
+
+**Correct (re-runs only when id changes):**
+
+```tsx
+useEffect(() => {
+ console.log(user.id)
+}, [user.id])
+```
+
+**For derived state, compute outside effect:**
+
+```tsx
+// Incorrect: runs on width=767, 766, 765...
+useEffect(() => {
+ if (width < 768) {
+ enableMobileMode()
+ }
+}, [width])
+
+// Correct: runs only on boolean transition
+const isMobile = width < 768
+useEffect(() => {
+ if (isMobile) {
+ enableMobileMode()
+ }
+}, [isMobile])
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rerender-derived-state.md b/.cursor/skills/vercel-react-best-practices/rules/rerender-derived-state.md
new file mode 100644
index 0000000..a15177c
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rerender-derived-state.md
@@ -0,0 +1,29 @@
+---
+title: Subscribe to Derived State
+impact: MEDIUM
+impactDescription: reduces re-render frequency
+tags: rerender, derived-state, media-query, optimization
+---
+
+## Subscribe to Derived State
+
+Subscribe to derived boolean state instead of continuous values to reduce re-render frequency.
+
+**Incorrect (re-renders on every pixel change):**
+
+```tsx
+function Sidebar() {
+ const width = useWindowWidth() // updates continuously
+ const isMobile = width < 768
+ return
+}
+```
+
+**Correct (re-renders only when boolean changes):**
+
+```tsx
+function Sidebar() {
+ const isMobile = useMediaQuery('(max-width: 767px)')
+ return
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md b/.cursor/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md
new file mode 100644
index 0000000..ac77927
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md
@@ -0,0 +1,77 @@
+---
+title: Use Functional setState Updates
+impact: MEDIUM
+impactDescription: prevents stale closures and unnecessary callback recreations
+tags: react, hooks, useState, useCallback, callbacks, closures
+---
+
+## Use Functional setState Updates
+
+When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.
+
+**Incorrect (requires state as dependency):**
+
+```tsx
+function TodoList() {
+ const [items, setItems] = useState(initialItems)
+
+ // Callback must depend on items, recreated on every items change
+ const addItems = useCallback(
+ (newItems: Item[]) => {
+ setItems([...items, ...newItems])
+ },
+ [items]
+ ) // ❌ items dependency causes recreations
+
+ // Risk of stale closure if dependency is forgotten
+ const removeItem = useCallback((id: string) => {
+ setItems(items.filter(item => item.id !== id))
+ }, []) // ❌ Missing items dependency - will use stale items!
+
+ return
+}
+```
+
+The first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.
+
+**Correct (stable callbacks, no stale closures):**
+
+```tsx
+function TodoList() {
+ const [items, setItems] = useState(initialItems)
+
+ // Stable callback, never recreated
+ const addItems = useCallback((newItems: Item[]) => {
+ setItems(curr => [...curr, ...newItems])
+ }, []) // ✅ No dependencies needed
+
+ // Always uses latest state, no stale closure risk
+ const removeItem = useCallback((id: string) => {
+ setItems(curr => curr.filter(item => item.id !== id))
+ }, []) // ✅ Safe and stable
+
+ return
+}
+```
+
+**Benefits:**
+
+1. **Stable callback references** - Callbacks don't need to be recreated when state changes
+2. **No stale closures** - Always operates on the latest state value
+3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks
+4. **Prevents bugs** - Eliminates the most common source of React closure bugs
+
+**When to use functional updates:**
+
+- Any setState that depends on the current state value
+- Inside useCallback/useMemo when state is needed
+- Event handlers that reference state
+- Async operations that update state
+
+**When direct updates are fine:**
+
+- Setting state to a static value: `setCount(0)`
+- Setting state from props/arguments only: `setName(newName)`
+- State doesn't depend on previous value
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md b/.cursor/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md
new file mode 100644
index 0000000..541242e
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md
@@ -0,0 +1,56 @@
+---
+title: Use Lazy State Initialization
+impact: MEDIUM
+impactDescription: wasted computation on every render
+tags: react, hooks, useState, performance, initialization
+---
+
+## Use Lazy State Initialization
+
+Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.
+
+**Incorrect (runs on every render):**
+
+```tsx
+function FilteredList({ items }: { items: Item[] }) {
+ // buildSearchIndex() runs on EVERY render, even after initialization
+ const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
+ const [query, setQuery] = useState('')
+
+ // When query changes, buildSearchIndex runs again unnecessarily
+ return
+}
+
+function UserProfile() {
+ // JSON.parse runs on every render
+ const [settings, setSettings] = useState(JSON.parse(localStorage.getItem('settings') || '{}'))
+
+ return
+}
+```
+
+**Correct (runs only once):**
+
+```tsx
+function FilteredList({ items }: { items: Item[] }) {
+ // buildSearchIndex() runs ONLY on initial render
+ const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
+ const [query, setQuery] = useState('')
+
+ return
+}
+
+function UserProfile() {
+ // JSON.parse runs only on initial render
+ const [settings, setSettings] = useState(() => {
+ const stored = localStorage.getItem('settings')
+ return stored ? JSON.parse(stored) : {}
+ })
+
+ return
+}
+```
+
+Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.
+
+For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rerender-memo.md b/.cursor/skills/vercel-react-best-practices/rules/rerender-memo.md
new file mode 100644
index 0000000..f8982ab
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rerender-memo.md
@@ -0,0 +1,44 @@
+---
+title: Extract to Memoized Components
+impact: MEDIUM
+impactDescription: enables early returns
+tags: rerender, memo, useMemo, optimization
+---
+
+## Extract to Memoized Components
+
+Extract expensive work into memoized components to enable early returns before computation.
+
+**Incorrect (computes avatar even when loading):**
+
+```tsx
+function Profile({ user, loading }: Props) {
+ const avatar = useMemo(() => {
+ const id = computeAvatarId(user)
+ return
+ }, [user])
+
+ if (loading) return
+ return {avatar}
+}
+```
+
+**Correct (skips computation when loading):**
+
+```tsx
+const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
+ const id = useMemo(() => computeAvatarId(user), [user])
+ return
+})
+
+function Profile({ user, loading }: Props) {
+ if (loading) return
+ return (
+
+
+
+ )
+}
+```
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/rerender-transitions.md b/.cursor/skills/vercel-react-best-practices/rules/rerender-transitions.md
new file mode 100644
index 0000000..d99f43f
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/rerender-transitions.md
@@ -0,0 +1,40 @@
+---
+title: Use Transitions for Non-Urgent Updates
+impact: MEDIUM
+impactDescription: maintains UI responsiveness
+tags: rerender, transitions, startTransition, performance
+---
+
+## Use Transitions for Non-Urgent Updates
+
+Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness.
+
+**Incorrect (blocks UI on every scroll):**
+
+```tsx
+function ScrollTracker() {
+ const [scrollY, setScrollY] = useState(0)
+ useEffect(() => {
+ const handler = () => setScrollY(window.scrollY)
+ window.addEventListener('scroll', handler, { passive: true })
+ return () => window.removeEventListener('scroll', handler)
+ }, [])
+}
+```
+
+**Correct (non-blocking updates):**
+
+```tsx
+import { startTransition } from 'react'
+
+function ScrollTracker() {
+ const [scrollY, setScrollY] = useState(0)
+ useEffect(() => {
+ const handler = () => {
+ startTransition(() => setScrollY(window.scrollY))
+ }
+ window.addEventListener('scroll', handler, { passive: true })
+ return () => window.removeEventListener('scroll', handler)
+ }, [])
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/server-after-nonblocking.md b/.cursor/skills/vercel-react-best-practices/rules/server-after-nonblocking.md
new file mode 100644
index 0000000..9fec0e5
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/server-after-nonblocking.md
@@ -0,0 +1,73 @@
+---
+title: Use after() for Non-Blocking Operations
+impact: MEDIUM
+impactDescription: faster response times
+tags: server, async, logging, analytics, side-effects
+---
+
+## Use after() for Non-Blocking Operations
+
+Use Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.
+
+**Incorrect (blocks response):**
+
+```tsx
+import { logUserAction } from '@/app/utils'
+
+export async function POST(request: Request) {
+ // Perform mutation
+ await updateDatabase(request)
+
+ // Logging blocks the response
+ const userAgent = request.headers.get('user-agent') || 'unknown'
+ await logUserAction({ userAgent })
+
+ return new Response(JSON.stringify({ status: 'success' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ })
+}
+```
+
+**Correct (non-blocking):**
+
+```tsx
+import { after } from 'next/server'
+import { headers, cookies } from 'next/headers'
+import { logUserAction } from '@/app/utils'
+
+export async function POST(request: Request) {
+ // Perform mutation
+ await updateDatabase(request)
+
+ // Log after response is sent
+ after(async () => {
+ const userAgent = (await headers()).get('user-agent') || 'unknown'
+ const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
+
+ logUserAction({ sessionCookie, userAgent })
+ })
+
+ return new Response(JSON.stringify({ status: 'success' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ })
+}
+```
+
+The response is sent immediately while logging happens in the background.
+
+**Common use cases:**
+
+- Analytics tracking
+- Audit logging
+- Sending notifications
+- Cache invalidation
+- Cleanup tasks
+
+**Important notes:**
+
+- `after()` runs even if the response fails or redirects
+- Works in Server Actions, Route Handlers, and Server Components
+
+Reference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)
diff --git a/.cursor/skills/vercel-react-best-practices/rules/server-cache-lru.md b/.cursor/skills/vercel-react-best-practices/rules/server-cache-lru.md
new file mode 100644
index 0000000..4833361
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/server-cache-lru.md
@@ -0,0 +1,41 @@
+---
+title: Cross-Request LRU Caching
+impact: HIGH
+impactDescription: caches across requests
+tags: server, cache, lru, cross-request
+---
+
+## Cross-Request LRU Caching
+
+`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.
+
+**Implementation:**
+
+```typescript
+import { LRUCache } from 'lru-cache'
+
+const cache = new LRUCache({
+ max: 1000,
+ ttl: 5 * 60 * 1000, // 5 minutes
+})
+
+export async function getUser(id: string) {
+ const cached = cache.get(id)
+ if (cached) return cached
+
+ const user = await db.user.findUnique({ where: { id } })
+ cache.set(id, user)
+ return user
+}
+
+// Request 1: DB query, result cached
+// Request 2: cache hit, no DB query
+```
+
+Use when sequential user actions hit multiple endpoints needing the same data within seconds.
+
+**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.
+
+**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.
+
+Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)
diff --git a/.cursor/skills/vercel-react-best-practices/rules/server-cache-react.md b/.cursor/skills/vercel-react-best-practices/rules/server-cache-react.md
new file mode 100644
index 0000000..aa56a47
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/server-cache-react.md
@@ -0,0 +1,26 @@
+---
+title: Per-Request Deduplication with React.cache()
+impact: MEDIUM
+impactDescription: deduplicates within request
+tags: server, cache, react-cache, deduplication
+---
+
+## Per-Request Deduplication with React.cache()
+
+Use `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.
+
+**Usage:**
+
+```typescript
+import { cache } from 'react'
+
+export const getCurrentUser = cache(async () => {
+ const session = await auth()
+ if (!session?.user?.id) return null
+ return await db.user.findUnique({
+ where: { id: session.user.id },
+ })
+})
+```
+
+Within a single request, multiple calls to `getCurrentUser()` execute the query only once.
diff --git a/.cursor/skills/vercel-react-best-practices/rules/server-parallel-fetching.md b/.cursor/skills/vercel-react-best-practices/rules/server-parallel-fetching.md
new file mode 100644
index 0000000..5261f08
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/server-parallel-fetching.md
@@ -0,0 +1,79 @@
+---
+title: Parallel Data Fetching with Component Composition
+impact: CRITICAL
+impactDescription: eliminates server-side waterfalls
+tags: server, rsc, parallel-fetching, composition
+---
+
+## Parallel Data Fetching with Component Composition
+
+React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.
+
+**Incorrect (Sidebar waits for Page's fetch to complete):**
+
+```tsx
+export default async function Page() {
+ const header = await fetchHeader()
+ return (
+
+ )
+}
+
+async function Sidebar() {
+ const items = await fetchSidebarItems()
+ return {items.map(renderItem)}
+}
+```
+
+**Correct (both fetch simultaneously):**
+
+```tsx
+async function Header() {
+ const data = await fetchHeader()
+ return {data}
+}
+
+async function Sidebar() {
+ const items = await fetchSidebarItems()
+ return {items.map(renderItem)}
+}
+
+export default function Page() {
+ return (
+
+
+
+
+ )
+}
+```
+
+**Alternative with children prop:**
+
+```tsx
+async function Layout({ children }: { children: ReactNode }) {
+ const header = await fetchHeader()
+ return (
+
+
{header}
+ {children}
+
+ )
+}
+
+async function Sidebar() {
+ const items = await fetchSidebarItems()
+ return {items.map(renderItem)}
+}
+
+export default function Page() {
+ return (
+
+
+
+ )
+}
+```
diff --git a/.cursor/skills/vercel-react-best-practices/rules/server-serialization.md b/.cursor/skills/vercel-react-best-practices/rules/server-serialization.md
new file mode 100644
index 0000000..a2bdb21
--- /dev/null
+++ b/.cursor/skills/vercel-react-best-practices/rules/server-serialization.md
@@ -0,0 +1,38 @@
+---
+title: Minimize Serialization at RSC Boundaries
+impact: HIGH
+impactDescription: reduces data transfer size
+tags: server, rsc, serialization, props
+---
+
+## Minimize Serialization at RSC Boundaries
+
+The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.
+
+**Incorrect (serializes all 50 fields):**
+
+```tsx
+async function Page() {
+ const user = await fetchUser() // 50 fields
+ return
+}
+
+;('use client')
+function Profile({ user }: { user: User }) {
+ return {user.name}
// uses 1 field
+}
+```
+
+**Correct (serializes only 1 field):**
+
+```tsx
+async function Page() {
+ const user = await fetchUser()
+ return
+}
+
+;('use client')
+function Profile({ name }: { name: string }) {
+ return {name}
+}
+```
diff --git a/.cursor/skills/web-design-guidelines/SKILL.md b/.cursor/skills/web-design-guidelines/SKILL.md
new file mode 100644
index 0000000..d045dfc
--- /dev/null
+++ b/.cursor/skills/web-design-guidelines/SKILL.md
@@ -0,0 +1,40 @@
+---
+name: web-design-guidelines
+description: Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
+argument-hint:
+metadata:
+ author: vercel
+ version: '1.0.0'
+---
+
+# Web Interface Guidelines
+
+Review files for compliance with Web Interface Guidelines.
+
+## How It Works
+
+1. Fetch the latest guidelines from the source URL below
+2. Read the specified files (or prompt user for files/pattern)
+3. Check against all rules in the fetched guidelines
+4. Output findings in the terse `file:line` format
+
+## Guidelines Source
+
+Fetch fresh guidelines before each review:
+
+```
+https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md
+```
+
+Use WebFetch to retrieve the latest rules. The fetched content contains all the rules and output format instructions.
+
+## Usage
+
+When a user provides a file or pattern argument:
+
+1. Fetch guidelines from the source URL above
+2. Read the specified files
+3. Apply all rules from the fetched guidelines
+4. Output findings using the format specified in the guidelines
+
+If no files specified, ask the user which files to review.
diff --git a/.cursor/skills/world-id-sdk/SKILL.md b/.cursor/skills/world-id-sdk/SKILL.md
new file mode 100644
index 0000000..cac2e88
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/SKILL.md
@@ -0,0 +1,235 @@
+---
+name: world-id-sdk
+description: >-
+ Comprehensive guide for using the World ID SDK (MiniKit) in mini-apps. Includes all commands
+ for verification, payments, authentication, transactions, chat, and more. Apps in this repository
+ MUST use the SDK for all World App integrations.
+license: MIT
+compatibility: React 18+, Next.js, TypeScript, World App
+metadata:
+ author: Worldcoin
+ version: '1.0.0'
+---
+
+# World ID SDK (MiniKit) - Commands Reference
+
+This document provides comprehensive documentation for all MiniKit commands available in World App mini-apps. The SDK enables your mini-app to interact with World App features including World ID verification, payments, wallet authentication, transactions, chat, and more.
+
+## When to Apply
+
+Reference these guidelines when:
+
+- Building mini-apps that integrate with World App
+- Implementing World ID verification flows
+- Adding payment functionality (WLD, USDC)
+- Setting up wallet authentication (Sign in with Ethereum)
+- Interacting with smart contracts
+- Implementing chat or messaging features
+- Requesting user permissions (notifications, microphone)
+- Sending notifications or haptic feedback
+- Sharing content or contacts
+
+## Initialization
+
+Before using any commands, MiniKit must be installed. Use the `MiniKitProvider` component:
+
+```tsx
+'use client'
+
+import { MiniKit } from '@worldcoin/minikit-js'
+import { ReactNode, useEffect } from 'react'
+
+export default function MiniKitProvider({ children }: { children: ReactNode }) {
+ useEffect(() => {
+ MiniKit.install()
+ }, [])
+
+ return <>{children}>
+}
+```
+
+Wrap your app root with the provider:
+
+```tsx
+import MiniKitProvider from '@/providers/minikit-provider'
+
+export default function RootLayout({ children }) {
+ return {children}
+}
+```
+
+**MUST**: Always check if MiniKit is installed before using commands:
+
+```tsx
+if (!MiniKit.isInstalled()) {
+ return // Handle case where World App is not available
+}
+```
+
+## Command Patterns
+
+MiniKit commands are available in two patterns:
+
+### 1. Async Pattern (Recommended)
+
+Commands return promises that resolve with the result:
+
+```tsx
+const { finalPayload } = await MiniKit.commandsAsync.verify({
+ action: 'my-action',
+})
+
+if (finalPayload.status === 'error') {
+ // Handle error
+ return
+}
+
+// Handle success
+```
+
+### 2. Event-Based Pattern
+
+Commands dispatch events that you subscribe to:
+
+```tsx
+// Send command
+MiniKit.commands.verify({ action: 'my-action' })
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(ResponseEvent.MiniAppVerifyAction, response => {
+ if (response.status === 'error') {
+ // Handle error
+ return
+ }
+ // Handle success
+ })
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppVerifyAction)
+ }
+}, [])
+```
+
+**DO**: Use async pattern for simpler code flow
+**DO**: Use event-based pattern for complex state management
+**DON'T**: Mix both patterns for the same command
+
+## Commands Overview
+
+| Command | Description | Use Case |
+| ------------------------ | --------------------------------------- | ------------------------------------------------ |
+| **Verify** | Request a World ID proof | Gating features behind unique human verification |
+| **Pay** | Request a payment | Processing WLD or USDC payments |
+| **Wallet Auth** | Authenticate via Sign in with Ethereum | User authentication with wallet |
+| **Send Transaction** | Interact with smart contracts | Executing blockchain transactions |
+| **Sign Message** | Sign personal messages | Message authentication |
+| **Sign Typed Data** | Sign EIP-712 payloads | Structured data signing |
+| **Chat** | Send messages and create groups | World Chat integration |
+| **Share Contacts** | Share user contacts (username only) | Contact sharing features |
+| **Request Permission** | Request notifications/microphone access | Permission management |
+| **Get Permissions** | Get current user permissions | Check permission status |
+| **Send Notifications** | Send push notifications | User engagement |
+| **Send Haptic Feedback** | Send haptic feedback | Enhanced UX feedback |
+| **Share** | Native OS share modal | Content sharing |
+
+## Command Documentation
+
+### Verification & Authentication
+
+- `rules/command-verify.md` - World ID verification with incognito actions
+- `rules/command-wallet-auth.md` - Sign in with Ethereum authentication
+
+### Payments & Transactions
+
+- `rules/command-pay.md` - Payment processing (WLD, USDC)
+- `rules/command-send-transaction.md` - Smart contract interactions
+
+### Signing
+
+- `rules/command-sign-message.md` - Personal message signing
+- `rules/command-sign-typed-data.md` - EIP-712 typed data signing
+
+### Communication
+
+- `rules/command-chat.md` - World Chat messaging
+- `rules/command-share-contacts.md` - Contact sharing
+
+### Permissions & Notifications
+
+- `rules/command-request-permission.md` - Request permissions
+- `rules/command-get-permissions.md` - Get current permissions
+- `rules/command-send-notifications.md` - Send push notifications
+
+### User Experience
+
+- `rules/command-haptics.md` - Haptic feedback
+- `rules/command-share.md` - Native OS share modal
+
+## Setup & Best Practices
+
+- `rules/initialization.md` - SDK setup and initialization patterns
+- `rules/best-practices-security.md` - Security guidelines
+- `rules/best-practices-error-handling.md` - Error handling patterns
+
+## Quick Reference
+
+### Always Check Installation
+
+```tsx
+if (!MiniKit.isInstalled()) {
+ // Handle gracefully
+ return
+}
+```
+
+### Always Verify in Backend
+
+For sensitive operations (verify, pay), **MUST** verify responses in your backend:
+
+```tsx
+// Frontend
+const { finalPayload } = await MiniKit.commandsAsync.verify({
+ action: 'my-action',
+})
+
+// Backend verification
+const verifyResponse = await fetch('/api/verify', {
+ method: 'POST',
+ body: JSON.stringify({ payload: finalPayload }),
+})
+```
+
+### Error Handling
+
+Always check response status:
+
+```tsx
+if (finalPayload.status === 'error') {
+ console.error('Error:', finalPayload.error_code)
+ // Handle error appropriately
+ return
+}
+```
+
+## Key Guidelines
+
+- **MUST**: Always verify sensitive operations (verify, pay) in backend
+- **MUST**: Check `MiniKit.isInstalled()` before using commands
+- **MUST**: Handle error responses with proper status checking
+- **DO**: Use async pattern for simpler code flow
+- **DO**: Use event-based pattern for complex state management
+- **DON'T**: Trust frontend responses for sensitive operations
+- **DON'T**: Skip error handling
+- **DON'T**: Use commands without checking if MiniKit is installed
+
+## References
+
+- [World Mini Apps Documentation](https://docs.world.org/mini-apps/quick-start/commands)
+- [MiniKit JS Package](https://www.npmjs.com/package/@worldcoin/minikit-js)
+
+---
+
+_Last updated: Based on World App Mini Apps documentation_
diff --git a/.cursor/skills/world-id-sdk/rules/best-practices-error-handling.md b/.cursor/skills/world-id-sdk/rules/best-practices-error-handling.md
new file mode 100644
index 0000000..bd6f794
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/best-practices-error-handling.md
@@ -0,0 +1,139 @@
+---
+title: Error Handling Best Practices
+category: best-practices
+tags: error-handling, errors, status, validation
+---
+
+## Error Handling Best Practices
+
+Proper error handling patterns for MiniKit commands to ensure robust mini-app behavior.
+
+## When to Apply
+
+- Implementing any MiniKit command
+- Handling user cancellations
+- Managing network failures
+- Processing command responses
+
+## DOs
+
+- **MUST**: Always check `status` field in command responses
+- **MUST**: Handle both `'success'` and `'error'` status cases
+- **DO**: Check `MiniKit.isInstalled()` before commands
+- **DO**: Provide user-friendly error messages
+- **DO**: Log errors for debugging (avoid logging sensitive data)
+- **DO**: Handle user cancellations gracefully
+- **DO**: Use try-catch for async commands
+- **DO**: Clean up event listeners in useEffect cleanup
+
+## DONTs
+
+- **DON'T**: Assume commands always succeed
+- **DON'T**: Skip error status checks
+- **DON'T**: Show technical error codes to users
+- **DON'T**: Ignore user cancellations
+- **DON'T**: Leave event listeners subscribed after component unmount
+
+## Examples
+
+### Async Pattern Error Handling
+
+```tsx
+const handleVerify = async () => {
+ if (!MiniKit.isInstalled()) {
+ toast.error('World App is required for this feature')
+ return
+ }
+
+ try {
+ const { finalPayload } = await MiniKit.commandsAsync.verify({
+ action: 'my-action',
+ })
+
+ if (finalPayload.status === 'error') {
+ // Handle specific error codes
+ switch (finalPayload.error_code) {
+ case 'user_cancelled':
+ // User cancelled - don't show error
+ return
+ case 'invalid_action':
+ toast.error('Invalid verification action')
+ break
+ default:
+ toast.error('Verification failed. Please try again.')
+ }
+ return
+ }
+
+ // Handle success
+ if (finalPayload.status === 'success') {
+ // Process verification
+ }
+ } catch (error) {
+ console.error('Verify command error:', error)
+ toast.error('An unexpected error occurred')
+ }
+}
+```
+
+### Event-Based Pattern Error Handling
+
+```tsx
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ const handleResponse = (response: MiniAppVerifyActionPayload) => {
+ if (response.status === 'error') {
+ if (response.error_code === 'user_cancelled') {
+ // User cancelled - handle gracefully
+ return
+ }
+ toast.error('Verification failed')
+ return
+ }
+
+ // Handle success
+ if (response.status === 'success') {
+ // Process verification
+ }
+ }
+
+ MiniKit.subscribe(ResponseEvent.MiniAppVerifyAction, handleResponse)
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppVerifyAction)
+ }
+}, [])
+```
+
+### Common Error Codes
+
+```tsx
+// Handle common error scenarios
+if (finalPayload.status === 'error') {
+ const errorCode = finalPayload.error_code
+
+ if (errorCode === 'user_cancelled') {
+ // User cancelled - normal behavior, don't show error
+ return
+ }
+
+ if (errorCode === 'not_installed') {
+ toast.error('Please open this in World App')
+ return
+ }
+
+ if (errorCode === 'invalid_action') {
+ toast.error('Invalid action. Please contact support.')
+ return
+ }
+
+ // Generic error handling
+ toast.error('Something went wrong. Please try again.')
+}
+```
+
+## References
+
+- [World Mini Apps - Responses](https://docs.world.org/mini-apps/quick-start/responses)
+- [World Mini Apps - Errors](https://docs.world.org/mini-apps/technical-reference/errors)
diff --git a/.cursor/skills/world-id-sdk/rules/best-practices-security.md b/.cursor/skills/world-id-sdk/rules/best-practices-security.md
new file mode 100644
index 0000000..6969209
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/best-practices-security.md
@@ -0,0 +1,114 @@
+---
+title: Security Best Practices
+category: best-practices
+tags: security, verification, backend, validation
+---
+
+## Security Best Practices
+
+Critical security guidelines for using MiniKit commands safely in production.
+
+## When to Apply
+
+- Implementing verification flows
+- Processing payments
+- Handling authentication
+- Verifying proofs or signatures
+- Building production mini-apps
+
+## DOs
+
+- **MUST**: Always verify sensitive operations in your backend
+ - Verify proofs from `verify` command
+ - Verify payment confirmations from `pay` command
+ - Verify signatures from `walletAuth`, `signMessage`, `signTypedData`
+- **MUST**: Use environment variables for sensitive data (APP_ID, secrets)
+- **MUST**: Validate all user inputs before sending commands
+- **DO**: Use HTTPS for all API communications
+- **DO**: Implement rate limiting on backend verification endpoints
+- **DO**: Log security events for audit purposes
+- **DO**: Use nonces for authentication flows to prevent replay attacks
+
+## DONTs
+
+- **DON'T**: Trust frontend responses for sensitive operations
+- **DON'T**: Store sensitive data (secrets, private keys) in frontend code
+- **DON'T**: Skip backend verification for verify, pay, or auth commands
+- **DON'T**: Expose APP_ID or secrets in client-side code
+- **DON'T**: Accept proofs without verifying nullifier_hash uniqueness
+- **DON'T**: Process payments without backend confirmation
+
+## Examples
+
+### Backend Verification (Verify Command)
+
+```tsx
+// Frontend - app/page.tsx
+const { finalPayload } = await MiniKit.commandsAsync.verify({
+ action: 'voting-action',
+})
+
+if (finalPayload.status === 'error') {
+ return
+}
+
+// MUST verify in backend
+const verifyResponse = await fetch('/api/verify', {
+ method: 'POST',
+ body: JSON.stringify({
+ payload: finalPayload,
+ action: 'voting-action',
+ }),
+})
+
+// Backend - app/api/verify/route.ts
+import { verifyCloudProof } from '@worldcoin/minikit-js'
+
+export async function POST(req: NextRequest) {
+ const { payload, action } = await req.json()
+ const app_id = process.env.APP_ID // From environment, not frontend
+
+ const verifyRes = await verifyCloudProof(payload, app_id, action)
+
+ if (!verifyRes.success) {
+ return NextResponse.json({ error: 'Verification failed' }, { status: 400 })
+ }
+
+ // Only now perform the protected action
+ // e.g., record vote, grant access, etc.
+ return NextResponse.json({ success: true })
+}
+```
+
+### Secure Environment Variables
+
+```tsx
+// ❌ DON'T - Exposing secrets in frontend
+const APP_ID = 'app_staging_12345' // Visible in client bundle
+
+// ✅ DO - Use environment variables
+// .env.local (server-side only)
+APP_ID = app_staging_12345
+WLD_CLIENT_SECRET = your_secret_here
+
+// app/api/verify/route.ts
+const app_id = process.env.APP_ID // Only accessible server-side
+```
+
+### Nonce for Authentication
+
+```tsx
+// ✅ DO - Use nonces to prevent replay attacks
+const { nonce } = await fetchNonce() // Get unique nonce from backend
+
+const { finalPayload } = await MiniKit.commandsAsync.walletAuth({
+ nonce, // Include nonce in signature
+})
+
+// Backend verifies nonce hasn't been used before
+```
+
+## References
+
+- [World Mini Apps - Verify Command](https://docs.world.org/mini-apps/commands/verify)
+- [World ID Verification](https://docs.world.org/world-id/verify-proofs)
diff --git a/.cursor/skills/world-id-sdk/rules/command-chat.md b/.cursor/skills/world-id-sdk/rules/command-chat.md
new file mode 100644
index 0000000..4d87518
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-chat.md
@@ -0,0 +1,165 @@
+---
+title: Chat Command
+category: commands
+tags: chat, messaging, world-chat, communication
+---
+
+## Chat Command
+
+Send messages and create groups in World Chat. Enables messaging functionality within your mini-app.
+
+## When to Use
+
+- Implementing chat features
+- Sending messages to users
+- Creating group chats
+- Building communication features
+- User-to-user messaging
+
+**Example Use Cases:**
+
+- In-app messaging
+- Group discussions
+- Customer support chat
+- Community features
+- Direct messaging
+
+## API
+
+### Input
+
+```tsx
+export type ChatCommandInput = {
+ type: 'send_message' | 'create_group'
+ recipient?: string // Username for send_message
+ message?: string // Message content
+ groupName?: string // For create_group
+ members?: string[] // Usernames for group
+}
+```
+
+### Output
+
+```tsx
+type MiniAppChatSuccessPayload = {
+ status: 'success'
+ message_id?: string // For send_message
+ group_id?: string // For create_group
+}
+
+type MiniAppChatErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **DO**: Request chat permissions before using
+- **DO**: Handle user cancellations gracefully
+- **DO**: Validate usernames before sending
+- **DO**: Provide clear feedback on message status
+- **DO**: Handle errors appropriately
+
+## DONTs
+
+- **DON'T**: Use chat without requesting permissions first
+- **DON'T**: Send messages to invalid usernames
+- **DON'T**: Spam users with messages
+- **DON'T**: Skip error handling
+
+## Examples
+
+### Async Pattern - Send Message
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleSendMessage = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.chat({
+ type: 'send_message',
+ recipient: 'username',
+ message: 'Hello!',
+ })
+
+ if (finalPayload.status === 'error') {
+ if (finalPayload.error_code === 'user_cancelled') {
+ return // User cancelled
+ }
+ toast.error('Failed to send message')
+ return
+ }
+
+ // Message sent successfully
+ toast.success('Message sent!')
+}
+```
+
+### Async Pattern - Create Group
+
+```tsx
+const handleCreateGroup = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.chat({
+ type: 'create_group',
+ groupName: 'My Group',
+ members: ['user1', 'user2'],
+ })
+
+ if (finalPayload.status === 'error') {
+ toast.error('Failed to create group')
+ return
+ }
+
+ // Group created
+ console.log('Group ID:', finalPayload.group_id)
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppChatPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleSendMessage = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.chat({
+ type: 'send_message',
+ recipient: 'username',
+ message: 'Hello!',
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(ResponseEvent.MiniAppChat, (response: MiniAppChatPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ // Handle success
+ if (response.message_id) {
+ console.log('Message sent:', response.message_id)
+ }
+ })
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppChat)
+ }
+}, [])
+```
+
+## References
+
+- [World Mini Apps - Chat Command](https://docs.world.org/mini-apps/commands/chat)
diff --git a/.cursor/skills/world-id-sdk/rules/command-get-permissions.md b/.cursor/skills/world-id-sdk/rules/command-get-permissions.md
new file mode 100644
index 0000000..be29180
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-get-permissions.md
@@ -0,0 +1,131 @@
+---
+title: Get Permissions Command
+category: commands
+tags: get-permissions, permissions, check, status
+---
+
+## Get Permissions Command
+
+Get current user permissions status. Check if user has granted notifications or microphone access.
+
+## When to Use
+
+- Checking permission status before requesting
+- Determining if features are available
+- Showing appropriate UI based on permissions
+- Onboarding flows
+- Permission status checks
+
+**Example Use Cases:**
+
+- Check if notifications are enabled
+- Verify microphone access before audio features
+- Conditional UI based on permissions
+- Permission status display
+
+## API
+
+### Input
+
+```tsx
+// No input required
+```
+
+### Output
+
+```tsx
+type MiniAppGetPermissionsSuccessPayload = {
+ status: 'success'
+ notifications: 'granted' | 'denied' | 'not_requested'
+ microphone: 'granted' | 'denied' | 'not_requested'
+}
+
+type MiniAppGetPermissionsErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **DO**: Check permissions before requesting them
+- **DO**: Use permission status to show appropriate UI
+- **DO**: Handle all permission states (granted, denied, not_requested)
+- **DO**: Update UI when permissions change
+
+## DONTs
+
+- **DON'T**: Request permissions if already granted
+- **DON'T**: Assume permission status without checking
+- **DON'T**: Ignore permission status in UI
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const checkPermissions = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.getPermissions()
+
+ if (finalPayload.status === 'error') {
+ return
+ }
+
+ // Use permission status
+ if (finalPayload.notifications === 'granted') {
+ // Notifications enabled - show enabled UI
+ setNotificationsEnabled(true)
+ } else if (finalPayload.notifications === 'denied') {
+ // Notifications denied - show disabled UI with option to enable in settings
+ setNotificationsEnabled(false)
+ } else {
+ // Not requested yet - show request button
+ setShowRequestButton(true)
+ }
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppGetPermissionsPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const checkPermissions = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.getPermissions()
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(
+ ResponseEvent.MiniAppGetPermissions,
+ (response: MiniAppGetPermissionsPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ // Update UI based on permissions
+ setNotificationsStatus(response.notifications)
+ setMicrophoneStatus(response.microphone)
+ }
+ )
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppGetPermissions)
+ }
+}, [])
+```
+
+## References
+
+- [World Mini Apps - Get Permissions](https://docs.world.org/mini-apps/commands/get-permissions)
diff --git a/.cursor/skills/world-id-sdk/rules/command-haptics.md b/.cursor/skills/world-id-sdk/rules/command-haptics.md
new file mode 100644
index 0000000..086ec57
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-haptics.md
@@ -0,0 +1,145 @@
+---
+title: Haptics Command
+category: commands
+tags: haptics, haptic-feedback, tactile, ux
+---
+
+## Haptics Command
+
+Send haptic feedback to the user's device. Provides tactile feedback for enhanced user experience.
+
+## When to Use
+
+- Providing tactile feedback for actions
+- Enhancing user interactions
+- Confirming user actions
+- Error feedback
+- Success feedback
+
+**Example Use Cases:**
+
+- Button press feedback
+- Transaction confirmations
+- Error notifications
+- Success confirmations
+- Interactive feedback
+
+## API
+
+### Input
+
+```tsx
+export type SendHapticFeedbackCommandInput = {
+ type: 'light' | 'medium' | 'heavy' | 'success' | 'warning' | 'error'
+}
+```
+
+### Output
+
+```tsx
+type MiniAppSendHapticFeedbackSuccessPayload = {
+ status: 'success'
+}
+
+type MiniAppSendHapticFeedbackErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **DO**: Use haptics to enhance important interactions
+- **DO**: Match haptic type to action (success for success, error for errors)
+- **DO**: Use sparingly - don't overuse haptics
+- **DO**: Provide haptic feedback for critical actions
+
+## DONTs
+
+- **DON'T**: Overuse haptics (can be annoying)
+- **DON'T**: Use haptics for every action
+- **DON'T**: Ignore user device settings
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleSuccess = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ // Send success haptic
+ await MiniKit.commandsAsync.sendHapticFeedback({
+ type: 'success',
+ })
+
+ toast.success('Action completed!')
+}
+
+const handleError = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ // Send error haptic
+ await MiniKit.commandsAsync.sendHapticFeedback({
+ type: 'error',
+ })
+
+ toast.error('Something went wrong')
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent } from '@worldcoin/minikit-js'
+
+const triggerHaptic = (
+ type: 'light' | 'medium' | 'heavy' | 'success' | 'warning' | 'error'
+) => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.sendHapticFeedback({ type })
+}
+
+// Use in button handlers
+; {
+ triggerHaptic('medium')
+ handleAction()
+ }}
+>
+ Click Me
+
+```
+
+### Common Patterns
+
+```tsx
+// Success action
+await MiniKit.commandsAsync.sendHapticFeedback({ type: 'success' })
+
+// Error action
+await MiniKit.commandsAsync.sendHapticFeedback({ type: 'error' })
+
+// Warning
+await MiniKit.commandsAsync.sendHapticFeedback({ type: 'warning' })
+
+// Light feedback for subtle actions
+await MiniKit.commandsAsync.sendHapticFeedback({ type: 'light' })
+
+// Medium feedback for standard actions
+await MiniKit.commandsAsync.sendHapticFeedback({ type: 'medium' })
+
+// Heavy feedback for important actions
+await MiniKit.commandsAsync.sendHapticFeedback({ type: 'heavy' })
+```
+
+## References
+
+- [World Mini Apps - Send Haptic Feedback](https://docs.world.org/mini-apps/commands/send-haptic-feedback)
diff --git a/.cursor/skills/world-id-sdk/rules/command-pay.md b/.cursor/skills/world-id-sdk/rules/command-pay.md
new file mode 100644
index 0000000..49c3ec9
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-pay.md
@@ -0,0 +1,184 @@
+---
+title: Pay Command
+category: commands
+tags: pay, payment, wld, usdc, transaction
+---
+
+## Pay Command
+
+Request a payment from the user. Supports WLD (Worldcoin) and USDC payments.
+
+## When to Use
+
+- Processing payments in your mini-app
+- Accepting WLD or USDC payments
+- In-app purchases
+- Subscription payments
+- Donations or tips
+- Payment for services or goods
+
+**Example Use Cases:**
+
+- Pay for premium features
+- Purchase in-app items
+- Tip creators
+- Donate to causes
+- Pay for services
+
+## API
+
+### Input
+
+```tsx
+export type PayCommandInput = {
+ amount: string // Amount as string (e.g., "10.5")
+ currency: 'WLD' | 'USDC' // Currency type
+ recipient: string // Recipient address
+ memo?: string // Optional payment memo
+}
+```
+
+### Output
+
+```tsx
+type MiniAppPaySuccessPayload = {
+ status: 'success'
+ transaction_hash: string
+ amount: string
+ currency: 'WLD' | 'USDC'
+ recipient: string
+}
+
+type MiniAppPayErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **MUST**: Verify payment confirmation in backend before fulfilling order
+- **MUST**: Check transaction_hash on blockchain to confirm payment
+- **DO**: Display clear payment amount and recipient to user
+- **DO**: Include memo for payment context if helpful
+- **DO**: Handle user cancellations gracefully
+- **DO**: Show payment status to user
+- **DO**: Verify transaction on blockchain before completing order
+
+## DONTs
+
+- **DON'T**: Trust frontend payment responses - always verify in backend
+- **DON'T**: Fulfill orders before blockchain confirmation
+- **DON'T**: Skip transaction verification
+- **DON'T**: Process payments without backend confirmation
+- **DON'T**: Accept payments without verifying transaction_hash
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handlePayment = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.pay({
+ amount: '10.5',
+ currency: 'WLD',
+ recipient: '0x1234567890123456789012345678901234567890',
+ memo: 'Payment for premium feature',
+ })
+
+ if (finalPayload.status === 'error') {
+ if (finalPayload.error_code === 'user_cancelled') {
+ return // User cancelled payment
+ }
+ toast.error('Payment failed')
+ return
+ }
+
+ // Verify payment in backend
+ const verifyResponse = await fetch('/api/payment/verify', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ transaction_hash: finalPayload.transaction_hash,
+ amount: finalPayload.amount,
+ currency: finalPayload.currency,
+ }),
+ })
+
+ const verifyResult = await verifyResponse.json()
+ if (verifyResult.confirmed) {
+ // Payment confirmed - fulfill order
+ toast.success('Payment successful!')
+ }
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppPayPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handlePayment = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.pay({
+ amount: '10.5',
+ currency: 'WLD',
+ recipient: '0x1234567890123456789012345678901234567890',
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(ResponseEvent.MiniAppPay, async (response: MiniAppPayPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ // Verify payment in backend
+ const verifyResponse = await fetch('/api/payment/verify', {
+ method: 'POST',
+ body: JSON.stringify({
+ transaction_hash: response.transaction_hash,
+ }),
+ })
+ })
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppPay)
+ }
+}, [])
+```
+
+### Backend Verification
+
+```tsx
+// app/api/payment/verify/route.ts
+export async function POST(req: NextRequest) {
+ const { transaction_hash, amount, currency } = await req.json()
+
+ // Verify transaction on blockchain
+ const transaction = await verifyTransactionOnChain(transaction_hash)
+
+ if (!transaction || transaction.amount !== amount) {
+ return NextResponse.json({ confirmed: false }, { status: 400 })
+ }
+
+ // Mark payment as confirmed in database
+ // Fulfill order
+ return NextResponse.json({ confirmed: true })
+}
+```
+
+## References
+
+- [World Mini Apps - Pay Command](https://docs.world.org/mini-apps/commands/pay)
diff --git a/.cursor/skills/world-id-sdk/rules/command-request-permission.md b/.cursor/skills/world-id-sdk/rules/command-request-permission.md
new file mode 100644
index 0000000..1ca47fd
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-request-permission.md
@@ -0,0 +1,144 @@
+---
+title: Request Permission Command
+category: commands
+tags: request-permission, permissions, notifications, microphone
+---
+
+## Request Permission Command
+
+Request permissions from the user (notifications, microphone access). Required before using certain features.
+
+## When to Use
+
+- Requesting notification permissions
+- Requesting microphone access
+- Before using features that require permissions
+- Setting up push notifications
+- Enabling audio features
+
+**Example Use Cases:**
+
+- Request notification permission for push notifications
+- Request microphone permission for voice features
+- Setting up permissions before feature use
+- Onboarding permission requests
+
+## API
+
+### Input
+
+```tsx
+export type RequestPermissionCommandInput = {
+ permission: 'notifications' | 'microphone'
+}
+```
+
+### Output
+
+```tsx
+type MiniAppRequestPermissionSuccessPayload = {
+ status: 'success'
+ permission: 'notifications' | 'microphone'
+ granted: boolean
+}
+
+type MiniAppRequestPermissionErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **DO**: Request permissions at appropriate times (not immediately on load)
+- **DO**: Explain why permission is needed before requesting
+- **DO**: Handle denied permissions gracefully
+- **DO**: Check current permissions before requesting
+- **DO**: Provide fallback UI when permission is denied
+
+## DONTs
+
+- **DON'T**: Request permissions immediately on page load
+- **DON'T**: Request permissions without context
+- **DON'T**: Spam permission requests
+- **DON'T**: Assume permission is granted
+- **DON'T**: Block features completely if permission denied
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleRequestNotificationPermission = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ // Check current permission first
+ const { finalPayload: currentPerms } = await MiniKit.commandsAsync.getPermissions()
+ if (currentPerms.notifications === 'granted') {
+ toast.info('Notifications already enabled')
+ return
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.requestPermission({
+ permission: 'notifications',
+ })
+
+ if (finalPayload.status === 'error') {
+ toast.error('Failed to request permission')
+ return
+ }
+
+ if (finalPayload.granted) {
+ toast.success('Notifications enabled!')
+ } else {
+ toast.info('Notifications permission denied')
+ }
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppRequestPermissionPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleRequestPermission = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.requestPermission({
+ permission: 'notifications',
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(
+ ResponseEvent.MiniAppRequestPermission,
+ (response: MiniAppRequestPermissionPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ if (response.granted) {
+ console.log('Permission granted:', response.permission)
+ } else {
+ console.log('Permission denied:', response.permission)
+ }
+ }
+ )
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppRequestPermission)
+ }
+}, [])
+```
+
+## References
+
+- [World Mini Apps - Request Permission](https://docs.world.org/mini-apps/commands/request-permission)
diff --git a/.cursor/skills/world-id-sdk/rules/command-send-notifications.md b/.cursor/skills/world-id-sdk/rules/command-send-notifications.md
new file mode 100644
index 0000000..7871189
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-send-notifications.md
@@ -0,0 +1,147 @@
+---
+title: Send Notifications Command
+category: commands
+tags: send-notifications, notifications, push, alerts
+---
+
+## Send Notifications Command
+
+Send push notifications to users. Requires notification permission to be granted first.
+
+## When to Use
+
+- Sending push notifications
+- User engagement
+- Important updates
+- Reminders
+- Alerts
+
+**Example Use Cases:**
+
+- Transaction confirmations
+- Important updates
+- Reminders
+- Engagement notifications
+- Alert users of events
+
+## API
+
+### Input
+
+```tsx
+export type SendNotificationCommandInput = {
+ title: string
+ body: string
+ data?: Record // Optional notification data
+}
+```
+
+### Output
+
+```tsx
+type MiniAppSendNotificationSuccessPayload = {
+ status: 'success'
+ notification_id: string
+}
+
+type MiniAppSendNotificationErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **MUST**: Request notification permission before sending
+- **DO**: Send meaningful, relevant notifications
+- **DO**: Respect user preferences
+- **DO**: Handle errors gracefully
+- **DO**: Don't spam users with notifications
+
+## DONTs
+
+- **DON'T**: Send notifications without permission
+- **DON'T**: Send excessive notifications
+- **DON'T**: Send irrelevant notifications
+- **DON'T**: Ignore notification guidelines
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleSendNotification = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ // Check permission first
+ const { finalPayload: perms } = await MiniKit.commandsAsync.getPermissions()
+ if (perms.notifications !== 'granted') {
+ // Request permission first
+ const { finalPayload: permResult } = await MiniKit.commandsAsync.requestPermission({
+ permission: 'notifications',
+ })
+ if (!permResult.granted) {
+ toast.error('Notification permission required')
+ return
+ }
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.sendNotification({
+ title: 'Transaction Complete',
+ body: 'Your payment has been processed',
+ data: { transaction_id: '123' },
+ })
+
+ if (finalPayload.status === 'error') {
+ toast.error('Failed to send notification')
+ return
+ }
+
+ console.log('Notification sent:', finalPayload.notification_id)
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppSendNotificationPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleSendNotification = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.sendNotification({
+ title: 'Update Available',
+ body: 'A new version is available',
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(
+ ResponseEvent.MiniAppSendNotification,
+ (response: MiniAppSendNotificationPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ console.log('Notification sent:', response.notification_id)
+ }
+ )
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppSendNotification)
+ }
+}, [])
+```
+
+## References
+
+- [World Mini Apps - Send Notifications](https://docs.world.org/mini-apps/commands/how-to-send-notifications)
+- [Notification Guidelines](https://docs.world.org/mini-apps/guidelines/notification-guidelines)
diff --git a/.cursor/skills/world-id-sdk/rules/command-send-transaction.md b/.cursor/skills/world-id-sdk/rules/command-send-transaction.md
new file mode 100644
index 0000000..c5faace
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-send-transaction.md
@@ -0,0 +1,156 @@
+---
+title: Send Transaction Command
+category: commands
+tags: send-transaction, transaction, smart-contract, blockchain
+---
+
+## Send Transaction Command
+
+Interact with smart contracts by sending transactions. Allows your mini-app to execute blockchain transactions.
+
+## When to Use
+
+- Interacting with smart contracts
+- Executing blockchain transactions
+- Calling contract methods
+- Transferring tokens (beyond simple payments)
+- Complex DeFi operations
+
+**Example Use Cases:**
+
+- Swap tokens on DEX
+- Interact with NFT contracts
+- Execute complex smart contract functions
+- Batch transactions
+- DeFi protocol interactions
+
+## API
+
+### Input
+
+```tsx
+export type SendTransactionCommandInput = {
+ to: string // Contract or address to send to
+ value?: string // Amount in wei (for ETH/WLD transfers)
+ data?: string // Contract call data (ABI-encoded)
+ gasLimit?: string // Optional gas limit
+}
+```
+
+### Output
+
+```tsx
+type MiniAppSendTransactionSuccessPayload = {
+ status: 'success'
+ transaction_hash: string
+}
+
+type MiniAppSendTransactionErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **MUST**: Verify transaction on blockchain before considering it successful
+- **DO**: Provide clear transaction details to user before confirmation
+- **DO**: Estimate gas costs when possible
+- **DO**: Handle transaction failures gracefully
+- **DO**: Show transaction status to user
+- **DO**: Verify transaction_hash on blockchain
+
+## DONTs
+
+- **DON'T**: Trust frontend transaction responses without blockchain verification
+- **DON'T**: Skip transaction verification
+- **DON'T**: Send transactions without user understanding
+- **DON'T**: Process orders before blockchain confirmation
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleSendTransaction = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.sendTransaction({
+ to: '0xContractAddress...',
+ value: '0', // No ETH transfer
+ data: '0x...', // ABI-encoded function call
+ })
+
+ if (finalPayload.status === 'error') {
+ if (finalPayload.error_code === 'user_cancelled') {
+ return // User cancelled
+ }
+ toast.error('Transaction failed')
+ return
+ }
+
+ // Verify transaction on blockchain
+ const verifyResponse = await fetch('/api/transaction/verify', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ transaction_hash: finalPayload.transaction_hash,
+ }),
+ })
+
+ const verifyResult = await verifyResponse.json()
+ if (verifyResult.confirmed) {
+ toast.success('Transaction confirmed!')
+ }
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppSendTransactionPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleSendTransaction = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.sendTransaction({
+ to: '0xContractAddress...',
+ data: '0x...',
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(
+ ResponseEvent.MiniAppSendTransaction,
+ async (response: MiniAppSendTransactionPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ // Verify transaction
+ await fetch('/api/transaction/verify', {
+ method: 'POST',
+ body: JSON.stringify({
+ transaction_hash: response.transaction_hash,
+ }),
+ })
+ }
+ )
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppSendTransaction)
+ }
+}, [])
+```
+
+## References
+
+- [World Mini Apps - Send Transaction](https://docs.world.org/mini-apps/commands/send-transaction)
diff --git a/.cursor/skills/world-id-sdk/rules/command-share-contacts.md b/.cursor/skills/world-id-sdk/rules/command-share-contacts.md
new file mode 100644
index 0000000..2d074af
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-share-contacts.md
@@ -0,0 +1,131 @@
+---
+title: Share Contacts Command
+category: commands
+tags: share-contacts, contacts, sharing, username
+---
+
+## Share Contacts Command
+
+Share individual user contacts (username only) from World App. Allows users to share contact information.
+
+## When to Use
+
+- Sharing user contacts
+- Referral features
+- Inviting friends
+- Contact sharing functionality
+- Social features
+
+**Example Use Cases:**
+
+- "Invite friends" features
+- Sharing contacts between users
+- Referral programs
+- Social sharing
+- Contact discovery
+
+## API
+
+### Input
+
+```tsx
+export type ShareContactsCommandInput = {
+ usernames: string[] // Array of usernames to share
+}
+```
+
+### Output
+
+```tsx
+type MiniAppShareContactsSuccessPayload = {
+ status: 'success'
+ shared_count: number
+}
+
+type MiniAppShareContactsErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **DO**: Request permission before sharing contacts
+- **DO**: Validate usernames before sharing
+- **DO**: Respect user privacy
+- **DO**: Handle user cancellations gracefully
+- **DO**: Provide clear feedback on sharing status
+
+## DONTs
+
+- **DON'T**: Share contacts without user consent
+- **DON'T**: Share invalid usernames
+- **DON'T**: Spam users with contact sharing
+- **DON'T**: Skip permission requests
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleShareContacts = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.shareContacts({
+ usernames: ['user1', 'user2'],
+ })
+
+ if (finalPayload.status === 'error') {
+ if (finalPayload.error_code === 'user_cancelled') {
+ return // User cancelled
+ }
+ toast.error('Failed to share contacts')
+ return
+ }
+
+ toast.success(`Shared ${finalPayload.shared_count} contacts`)
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppShareContactsPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleShareContacts = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.shareContacts({
+ usernames: ['user1', 'user2'],
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(
+ ResponseEvent.MiniAppShareContacts,
+ (response: MiniAppShareContactsPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ console.log('Shared contacts:', response.shared_count)
+ }
+ )
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppShareContacts)
+ }
+}, [])
+```
+
+## References
+
+- [World Mini Apps - Share Contacts](https://docs.world.org/mini-apps/commands/share-contacts)
diff --git a/.cursor/skills/world-id-sdk/rules/command-share.md b/.cursor/skills/world-id-sdk/rules/command-share.md
new file mode 100644
index 0000000..6ff3838
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-share.md
@@ -0,0 +1,160 @@
+---
+title: Share Command
+category: commands
+tags: share, native-share, os-share, sharing
+---
+
+## Share Command
+
+Open the native OS share modal. Allows users to share content using their device's native sharing capabilities.
+
+## When to Use
+
+- Sharing content from your mini-app
+- Sharing links, text, or images
+- Social sharing features
+- Content distribution
+- User-generated content sharing
+
+**Example Use Cases:**
+
+- Share app link
+- Share content
+- Share achievements
+- Share results
+- Social sharing
+
+## API
+
+### Input
+
+```tsx
+export type ShareCommandInput = {
+ title?: string
+ text?: string
+ url?: string
+}
+```
+
+### Output
+
+```tsx
+type MiniAppShareSuccessPayload = {
+ status: 'success'
+ shared: boolean // Whether user actually shared
+}
+
+type MiniAppShareErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **DO**: Provide meaningful share content
+- **DO**: Include URL for easy sharing
+- **DO**: Handle user cancellations gracefully
+- **DO**: Use appropriate share text
+
+## DONTs
+
+- **DON'T**: Share empty or meaningless content
+- **DON'T**: Force users to share
+- **DON'T**: Assume user shared (check shared status)
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleShare = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.share({
+ title: 'Check out this mini-app!',
+ text: 'I found this amazing mini-app on World App',
+ url: 'https://myapp.com',
+ })
+
+ if (finalPayload.status === 'error') {
+ if (finalPayload.error_code === 'user_cancelled') {
+ return // User cancelled - normal behavior
+ }
+ toast.error('Failed to share')
+ return
+ }
+
+ if (finalPayload.shared) {
+ toast.success('Shared successfully!')
+ } else {
+ // User opened share modal but didn't share
+ }
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppSharePayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleShare = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.share({
+ title: 'My Achievement',
+ text: 'I just completed a challenge!',
+ url: 'https://myapp.com/achievement',
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(ResponseEvent.MiniAppShare, (response: MiniAppSharePayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ if (response.shared) {
+ console.log('User shared content')
+ }
+ })
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppShare)
+ }
+}, [])
+```
+
+### Sharing Different Content Types
+
+```tsx
+// Share link
+await MiniKit.commandsAsync.share({
+ url: 'https://myapp.com',
+ text: 'Check this out!',
+})
+
+// Share text
+await MiniKit.commandsAsync.share({
+ text: 'This is some text to share',
+})
+
+// Share with title
+await MiniKit.commandsAsync.share({
+ title: 'My App',
+ text: 'Description',
+ url: 'https://myapp.com',
+})
+```
+
+## References
+
+- [World Mini Apps - Share Command](https://docs.world.org/mini-apps/commands/share)
diff --git a/.cursor/skills/world-id-sdk/rules/command-sign-message.md b/.cursor/skills/world-id-sdk/rules/command-sign-message.md
new file mode 100644
index 0000000..b6a5e17
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-sign-message.md
@@ -0,0 +1,157 @@
+---
+title: Sign Message Command
+category: commands
+tags: sign-message, signature, authentication, wallet
+---
+
+## Sign Message Command
+
+Sign personal messages with the user's wallet. Used for message authentication and proving wallet ownership.
+
+## When to Use
+
+- Proving wallet ownership
+- Message authentication
+- Non-repudiation of messages
+- Custom authentication flows
+- Verifying user identity via signed messages
+
+**Example Use Cases:**
+
+- "Sign in with wallet" flows
+- Proving ownership of wallet address
+- Authenticating API requests
+- Message verification
+- Custom authentication schemes
+
+## API
+
+### Input
+
+```tsx
+export type SignMessageCommandInput = {
+ message: string // Message to sign
+}
+```
+
+### Output
+
+```tsx
+type MiniAppSignMessageSuccessPayload = {
+ status: 'success'
+ signature: string
+ message: string
+ address: string
+}
+
+type MiniAppSignMessageErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **MUST**: Verify signature in backend before trusting it
+- **DO**: Use clear, meaningful messages
+- **DO**: Include context in message (e.g., timestamp, nonce)
+- **DO**: Verify signature matches message and address
+- **DO**: Handle user cancellations gracefully
+
+## DONTs
+
+- **DON'T**: Trust frontend signature responses - always verify in backend
+- **DON'T**: Use signatures without verification
+- **DON'T**: Accept signatures for different messages
+- **DON'T**: Skip signature validation
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleSignMessage = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const message = `Sign in to MyApp\nTimestamp: ${Date.now()}`
+
+ const { finalPayload } = await MiniKit.commandsAsync.signMessage({
+ message,
+ })
+
+ if (finalPayload.status === 'error') {
+ if (finalPayload.error_code === 'user_cancelled') {
+ return // User cancelled
+ }
+ toast.error('Signing failed')
+ return
+ }
+
+ // Verify signature in backend
+ const verifyResponse = await fetch('/api/auth/verify-signature', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ signature: finalPayload.signature,
+ message: finalPayload.message,
+ address: finalPayload.address,
+ }),
+ })
+
+ const verifyResult = await verifyResponse.json()
+ if (verifyResult.valid) {
+ // Signature verified - authenticate user
+ }
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppSignMessagePayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleSignMessage = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.signMessage({
+ message: 'Sign in to MyApp',
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(
+ ResponseEvent.MiniAppSignMessage,
+ async (response: MiniAppSignMessagePayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ // Verify signature
+ await fetch('/api/auth/verify-signature', {
+ method: 'POST',
+ body: JSON.stringify({
+ signature: response.signature,
+ message: response.message,
+ address: response.address,
+ }),
+ })
+ }
+ )
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppSignMessage)
+ }
+}, [])
+```
+
+## References
+
+- [World Mini Apps - Sign Message](https://docs.world.org/mini-apps/commands/sign-message)
diff --git a/.cursor/skills/world-id-sdk/rules/command-sign-typed-data.md b/.cursor/skills/world-id-sdk/rules/command-sign-typed-data.md
new file mode 100644
index 0000000..941698f
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-sign-typed-data.md
@@ -0,0 +1,184 @@
+---
+title: Sign Typed Data Command
+category: commands
+tags: sign-typed-data, eip-712, typed-data, signature
+---
+
+## Sign Typed Data Command
+
+Sign EIP-712 typed data payloads with the user's wallet. Used for structured data signing with type safety.
+
+## When to Use
+
+- Signing structured data (EIP-712)
+- Type-safe message signing
+- Complex authentication flows
+- DeFi protocol interactions
+- Smart contract interactions requiring typed data
+
+**Example Use Cases:**
+
+- Signing orders for DEX
+- Authenticating structured requests
+- DeFi protocol interactions
+- Complex authentication schemes
+- Type-safe message signing
+
+## API
+
+### Input
+
+```tsx
+export type SignTypedDataCommandInput = {
+ domain: {
+ name: string
+ version: string
+ chainId: number
+ verifyingContract: string
+ }
+ types: Record>
+ primaryType: string
+ message: Record
+}
+```
+
+### Output
+
+```tsx
+type MiniAppSignTypedDataSuccessPayload = {
+ status: 'success'
+ signature: string
+ address: string
+}
+
+type MiniAppSignTypedDataErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **MUST**: Verify signature in backend before trusting it
+- **DO**: Use proper EIP-712 domain structure
+- **DO**: Define types correctly
+- **DO**: Verify signature matches typed data and address
+- **DO**: Handle user cancellations gracefully
+
+## DONTs
+
+- **DON'T**: Trust frontend signature responses - always verify in backend
+- **DON'T**: Use signatures without verification
+- **DON'T**: Skip signature validation
+- **DON'T**: Modify typed data after signing
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleSignTypedData = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const typedData = {
+ domain: {
+ name: 'MyApp',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0x...',
+ },
+ types: {
+ Message: [
+ { name: 'content', type: 'string' },
+ { name: 'timestamp', type: 'uint256' },
+ ],
+ },
+ primaryType: 'Message',
+ message: {
+ content: 'Hello World',
+ timestamp: Date.now(),
+ },
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.signTypedData(typedData)
+
+ if (finalPayload.status === 'error') {
+ if (finalPayload.error_code === 'user_cancelled') {
+ return // User cancelled
+ }
+ toast.error('Signing failed')
+ return
+ }
+
+ // Verify signature in backend
+ const verifyResponse = await fetch('/api/auth/verify-typed-data', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ signature: finalPayload.signature,
+ typedData,
+ address: finalPayload.address,
+ }),
+ })
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppSignTypedDataPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleSignTypedData = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.signTypedData({
+ domain: {
+ /* ... */
+ },
+ types: {
+ /* ... */
+ },
+ primaryType: 'Message',
+ message: {
+ /* ... */
+ },
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(
+ ResponseEvent.MiniAppSignTypedData,
+ async (response: MiniAppSignTypedDataPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ // Verify signature
+ await fetch('/api/auth/verify-typed-data', {
+ method: 'POST',
+ body: JSON.stringify({
+ signature: response.signature,
+ address: response.address,
+ }),
+ })
+ }
+ )
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppSignTypedData)
+ }
+}, [])
+```
+
+## References
+
+- [World Mini Apps - Sign Typed Data](https://docs.world.org/mini-apps/commands/sign-typed-data)
+- [EIP-712 Specification](https://eips.ethereum.org/EIPS/eip-712)
diff --git a/.cursor/skills/world-id-sdk/rules/command-verify.md b/.cursor/skills/world-id-sdk/rules/command-verify.md
new file mode 100644
index 0000000..cc04fe3
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-verify.md
@@ -0,0 +1,195 @@
+---
+title: Verify Command
+category: commands
+tags: verify, world-id, proof, incognito-action, verification
+---
+
+## Verify Command
+
+Request a World ID proof using incognito actions. This command lets you gate functionality behind a unique human check, ensuring only verified humans can perform actions.
+
+## When to Use
+
+- Gating features behind human verification
+- Preventing bot abuse (e.g., voting, submissions, access)
+- Limiting actions per verified user (using action limits)
+- Ensuring unique human participation
+- Building anti-bot mechanisms
+
+**Example Use Cases:**
+
+- Voting systems that require one vote per human
+- Access control for premium features
+- Submission limits (e.g., one submission per verified human)
+- Bot-free gaming experiences
+
+## API
+
+### Input
+
+```tsx
+export type VerifyCommandInput = {
+ action: string // Action ID from Developer Portal
+ signal?: string // Optional additional data
+ verification_level?: VerificationLevel // Default: Orb
+}
+```
+
+### Output
+
+```tsx
+type MiniAppVerifyActionSuccessPayload = {
+ status: 'success'
+ proof: string
+ merkle_root: string
+ nullifier_hash: string
+ verification_level: VerificationLevel
+ version: number
+}
+
+type MiniAppVerifyActionErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **MUST**: Create incognito action in Developer Portal first
+- **MUST**: Verify the proof in your backend before trusting it
+- **MUST**: Check `nullifier_hash` uniqueness to prevent double-use
+- **DO**: Use meaningful action IDs (e.g., 'voting-action', 'access-premium')
+- **DO**: Include signal data for additional context if needed
+- **DO**: Handle user cancellations gracefully
+- **DO**: Set appropriate action limits in Developer Portal
+
+## DONTs
+
+- **DON'T**: Trust frontend proof responses - always verify in backend
+- **DON'T**: Skip nullifier_hash uniqueness checks
+- **DON'T**: Use the same action ID for different features
+- **DON'T**: Process actions before backend verification succeeds
+- **DON'T**: Expose APP_ID in frontend code
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit, VerifyCommandInput, VerificationLevel } from '@worldcoin/minikit-js'
+
+const handleVerify = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ const verifyPayload: VerifyCommandInput = {
+ action: 'voting-action', // From Developer Portal
+ signal: '0x12312', // Optional
+ verification_level: VerificationLevel.Orb, // Orb | Device
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.verify(verifyPayload)
+
+ if (finalPayload.status === 'error') {
+ if (finalPayload.error_code === 'user_cancelled') {
+ return // User cancelled
+ }
+ console.error('Verify error:', finalPayload.error_code)
+ return
+ }
+
+ // MUST verify in backend
+ const verifyResponse = await fetch('/api/verify', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ payload: finalPayload,
+ action: 'voting-action',
+ signal: '0x12312',
+ }),
+ })
+
+ const verifyResponseJson = await verifyResponse.json()
+ if (verifyResponseJson.status === 200) {
+ // Verification successful - proceed with action
+ console.log('Verification success!')
+ }
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppVerifyActionPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleVerify = () => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.commands.verify({
+ action: 'voting-action',
+ verification_level: VerificationLevel.Orb,
+ })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(
+ ResponseEvent.MiniAppVerifyAction,
+ async (response: MiniAppVerifyActionPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ // Verify in backend
+ const verifyResponse = await fetch('/api/verify', {
+ method: 'POST',
+ body: JSON.stringify({
+ payload: response,
+ action: 'voting-action',
+ }),
+ })
+
+ const verifyResponseJson = await verifyResponse.json()
+ if (verifyResponseJson.status === 200) {
+ // Handle success
+ }
+ }
+ )
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppVerifyAction)
+ }
+}, [])
+```
+
+### Backend Verification
+
+```tsx
+// app/api/verify/route.ts
+import { NextRequest, NextResponse } from 'next/server'
+import { verifyCloudProof, ISuccessResult } from '@worldcoin/minikit-js'
+
+export async function POST(req: NextRequest) {
+ const { payload, action, signal } = await req.json()
+ const app_id = process.env.APP_ID as `app_${string}`
+
+ const verifyRes = await verifyCloudProof(payload, app_id, action, signal)
+
+ if (verifyRes.success) {
+ // Check nullifier_hash uniqueness in your database
+ // Perform the protected action
+ return NextResponse.json({ status: 200 })
+ } else {
+ return NextResponse.json({ verifyRes, status: 400 })
+ }
+}
+```
+
+## References
+
+- [World Mini Apps - Verify Command](https://docs.world.org/mini-apps/commands/verify)
+- [World ID Verification](https://docs.world.org/world-id/verify-proofs)
diff --git a/.cursor/skills/world-id-sdk/rules/command-wallet-auth.md b/.cursor/skills/world-id-sdk/rules/command-wallet-auth.md
new file mode 100644
index 0000000..4468c3e
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/command-wallet-auth.md
@@ -0,0 +1,187 @@
+---
+title: Wallet Auth Command
+category: commands
+tags: wallet-auth, authentication, siwe, sign-in-with-ethereum
+---
+
+## Wallet Auth Command
+
+Authenticate users via Sign in with Ethereum (SIWE). This command allows users to sign a message with their wallet to authenticate.
+
+## When to Use
+
+- Implementing wallet-based authentication
+- Sign in with Ethereum flows
+- User authentication with crypto wallets
+- Linking wallet addresses to user accounts
+- Building decentralized authentication
+
+**Example Use Cases:**
+
+- Login with wallet
+- Connect wallet to account
+- Verify wallet ownership
+- Decentralized identity authentication
+
+## API
+
+### Input
+
+```tsx
+export type WalletAuthCommandInput = {
+ nonce: string // Unique nonce from your backend
+}
+```
+
+### Output
+
+```tsx
+type MiniAppWalletAuthSuccessPayload = {
+ status: 'success'
+ address: string
+ signature: string
+ message: string
+ version: number
+}
+
+type MiniAppWalletAuthErrorPayload = {
+ status: 'error'
+ error_code: string
+}
+```
+
+## DOs
+
+- **MUST**: Generate unique nonce from backend for each auth attempt
+- **MUST**: Verify signature in backend before authenticating user
+- **MUST**: Check nonce hasn't been used before (prevent replay attacks)
+- **DO**: Use secure nonce generation (cryptographically random)
+- **DO**: Include expiration time in SIWE message
+- **DO**: Verify message format matches expected structure
+- **DO**: Handle user cancellations gracefully
+
+## DONTs
+
+- **DON'T**: Trust frontend signature responses - always verify in backend
+- **DON'T**: Reuse nonces across authentication attempts
+- **DON'T**: Skip signature verification
+- **DON'T**: Accept signatures without validating message content
+- **DON'T**: Store sensitive auth data in frontend
+
+## Examples
+
+### Async Pattern
+
+```tsx
+import { MiniKit } from '@worldcoin/minikit-js'
+
+const handleWalletAuth = async () => {
+ if (!MiniKit.isInstalled()) {
+ return
+ }
+
+ // Get nonce from backend
+ const { nonce } = await fetchNonce()
+ if (!nonce) {
+ console.error('No nonce found')
+ return
+ }
+
+ const { finalPayload: walletAuthPayload } = await MiniKit.commandsAsync.walletAuth({
+ nonce,
+ })
+
+ if (walletAuthPayload.status === 'error') {
+ if (walletAuthPayload.error_code === 'user_cancelled') {
+ return // User cancelled
+ }
+ console.warn(`walletAuth: ${walletAuthPayload.error_code}`)
+ return
+ }
+
+ // Verify signature in backend
+ const authResponse = await fetch('/api/auth/siwe', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ ...walletAuthPayload,
+ nonce,
+ }),
+ })
+
+ const authResult = await authResponse.json()
+ if (authResult.success) {
+ // User authenticated
+ }
+}
+```
+
+### Event-Based Pattern
+
+```tsx
+import { MiniKit, ResponseEvent, MiniAppWalletAuthPayload } from '@worldcoin/minikit-js'
+
+// Send command
+const handleWalletAuth = async () => {
+ if (!MiniKit.isInstalled()) return
+
+ const { nonce } = await fetchNonce()
+ MiniKit.commands.walletAuth({ nonce })
+}
+
+// Listen for response
+useEffect(() => {
+ if (!MiniKit.isInstalled()) return
+
+ MiniKit.subscribe(
+ ResponseEvent.MiniAppWalletAuth,
+ async (response: MiniAppWalletAuthPayload) => {
+ if (response.status === 'error') {
+ return
+ }
+
+ // Verify in backend
+ const authResponse = await fetch('/api/auth/siwe', {
+ method: 'POST',
+ body: JSON.stringify({
+ ...response,
+ nonce: await fetchNonce(),
+ }),
+ })
+ }
+ )
+
+ return () => {
+ MiniKit.unsubscribe(ResponseEvent.MiniAppWalletAuth)
+ }
+}, [])
+```
+
+### Backend Verification
+
+```tsx
+// app/api/auth/siwe/route.ts
+import { completeSiwe } from '@/utils/complete-siwe'
+
+export async function POST(req: NextRequest) {
+ const { nonce, address, signature, message, version } = await req.json()
+
+ const { isValid, siweMessageData } = await completeSiwe({
+ payload: { address, signature, message, version },
+ nonce,
+ })
+
+ if (!isValid || !siweMessageData.address) {
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
+ }
+
+ // Create or update user session
+ // Return auth token
+ return NextResponse.json({ success: true, address: siweMessageData.address })
+}
+```
+
+## References
+
+- [World Mini Apps - Wallet Authentication](https://docs.world.org/mini-apps/commands/wallet-auth)
+- [Sign in with Ethereum](https://docs.world.org/world-id/sign-in-with-ethereum)
diff --git a/.cursor/skills/world-id-sdk/rules/initialization.md b/.cursor/skills/world-id-sdk/rules/initialization.md
new file mode 100644
index 0000000..3c9e3a6
--- /dev/null
+++ b/.cursor/skills/world-id-sdk/rules/initialization.md
@@ -0,0 +1,89 @@
+---
+title: MiniKit Initialization
+category: setup
+tags: initialization, setup, install, provider
+---
+
+## Initialization
+
+Proper initialization of MiniKit is required before using any commands in your mini-app.
+
+## When to Use
+
+- Setting up a new mini-app
+- Ensuring MiniKit is available before command execution
+- Creating provider components for app-wide MiniKit access
+
+## API
+
+```tsx
+// Install MiniKit
+MiniKit.install(): void
+
+// Check if MiniKit is installed
+MiniKit.isInstalled(): boolean
+```
+
+## DOs
+
+- **DO**: Call `MiniKit.install()` in a `useEffect` hook in your root provider
+- **DO**: Check `MiniKit.isInstalled()` before using any commands
+- **DO**: Wrap your app with a provider component for consistent initialization
+- **DO**: Use client-side only code (`'use client'`) for MiniKit initialization
+
+## DONTs
+
+- **DON'T**: Call `MiniKit.install()` multiple times
+- **DON'T**: Use commands without checking if MiniKit is installed
+- **DON'T**: Initialize MiniKit in server components
+- **DON'T**: Assume MiniKit is always available (handle gracefully when not installed)
+
+## Examples
+
+### Basic Provider Setup
+
+```tsx
+'use client'
+
+import { MiniKit } from '@worldcoin/minikit-js'
+import { ReactNode, useEffect } from 'react'
+
+export default function MiniKitProvider({ children }: { children: ReactNode }) {
+ useEffect(() => {
+ MiniKit.install()
+ }, [])
+
+ return <>{children}>
+}
+```
+
+### Using in App Layout
+
+```tsx
+import MiniKitProvider from '@/providers/minikit-provider'
+
+export default function RootLayout({ children }) {
+ return {children}
+}
+```
+
+### Checking Before Commands
+
+```tsx
+const handleVerify = async () => {
+ if (!MiniKit.isInstalled()) {
+ console.warn('MiniKit is not installed')
+ // Show fallback UI or disable feature
+ return
+ }
+
+ const { finalPayload } = await MiniKit.commandsAsync.verify({
+ action: 'my-action',
+ })
+ // ...
+}
+```
+
+## References
+
+- [World Mini Apps - Initialization](https://docs.world.org/mini-apps/quick-start/initialization)
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..6a1c14c
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,23 @@
+# AgentBook
+AGENTBOOK_CONTRACT_ADDRESS=0x...
+AGENTBOOK_RPC_URL=https://mainnet.base.org
+NEXT_PUBLIC_AGENTBOOK_ADDRESS=0x...
+
+# Airdrop Contract
+AIRDROP_CONTRACT_ADDRESS=0x...
+AIRDROP_SIGNER_PRIVATE_KEY=0x...
+
+# Token
+TOKEN_ADDRESS=0x...
+CLAIM_AMOUNT=1000000000000000000
+
+# World ID RP
+RP_SIGNING_KEY=0x...
+RP_ID=rp_...
+
+# App
+NEXT_PUBLIC_CHAIN_ID=eip155:8453
+NEXT_PUBLIC_DOMAIN=localhost:3000
+
+# Database (Supabase PostgreSQL)
+DATABASE_URL=postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..5ffba16
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,126 @@
+name: CI
+
+on:
+ - pull_request
+
+permissions:
+ contents: read
+
+jobs:
+ format:
+ name: Format
+ runs-on: ubuntu-24.04
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
+ with:
+ version: 10
+
+ - name: Set up Node 20
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version: 20
+ cache: 'pnpm'
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Check formatting with Prettier
+ run: pnpm format:check
+
+ lint:
+ name: Lint
+ runs-on: ubuntu-24.04
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
+ with:
+ version: 10
+
+ - name: Set up Node 20
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version: 20
+ cache: 'pnpm'
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Lint
+ run: pnpm lint
+
+ typecheck:
+ name: Typecheck
+ runs-on: ubuntu-24.04
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
+ with:
+ version: 10
+
+ - name: Set up Node 20
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version: 20
+ cache: 'pnpm'
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Check Typescript
+ run: pnpm typecheck
+
+ spellcheck:
+ name: Spellcheck
+ runs-on: ubuntu-24.04
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
+ with:
+ version: 10
+
+ - name: Set up Node 20
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version: 20
+ cache: 'pnpm'
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Check spelling
+ run: pnpm spellcheck
+
+ test:
+ name: Test
+ runs-on: ubuntu-24.04
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
+ with:
+ version: 10
+ run_install: false
+
+ - name: Set up Node 20
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version: 20
+ cache: 'pnpm'
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Run tests
+ run: pnpm test
diff --git a/.github/workflows/relyance-sci.yml b/.github/workflows/relyance-sci.yml
index 1f1b3ca..6a60e8e 100644
--- a/.github/workflows/relyance-sci.yml
+++ b/.github/workflows/relyance-sci.yml
@@ -2,7 +2,7 @@ name: Relyance SCI Scan
on:
schedule:
- - cron: "4 0 * * *"
+ - cron: '58 0 * * *'
workflow_dispatch:
permissions:
@@ -12,7 +12,7 @@ jobs:
execute-relyance-sci:
name: Relyance SCI Job
runs-on:
- group: arc-public-large-amd64-runner
+ group: arc-large-amd64-runner
permissions:
contents: read
diff --git a/.github/workflows/security-scorecard-deploy.yml b/.github/workflows/security-scorecard-deploy.yml
new file mode 100644
index 0000000..943f1e0
--- /dev/null
+++ b/.github/workflows/security-scorecard-deploy.yml
@@ -0,0 +1,38 @@
+name: Security Scorecard Dispatch
+on:
+ workflow_dispatch:
+ # For Branch-Protection check. Only the default branch is supported. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+ branch_protection_rule:
+ # To guarantee Maintained check is occasionally updated. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+ schedule:
+ - cron: '58 14 * * *' # Run daily at 14:58
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+ analysis:
+ name: Scorecard analysis
+ runs-on:
+ group: arc-medium-amd64-runner
+ # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
+ if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
+ permissions:
+ # Needed to upload the results to code-scanning dashboard.
+ security-events: write
+ # Needed to publish results and get a badge (see publish_results below).
+ id-token: write
+ # Uncomment the permissions below if installing in a private repository.
+ contents: read
+ actions: read
+ # To allow GraphQL ListCommits to work
+ issues: read
+ pull-requests: read
+ # To detect SAST tools
+ checks: read
+ steps:
+ - name: Run Security Scorecard
+ uses: worldcoin/gh-actions/security/security-scorecard@main
+ # More information: https://github.com/worldcoin/gh-actions/tree/main/security/security-scorecard
diff --git a/.gitignore b/.gitignore
index 3c3629e..590cbee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,59 @@
-node_modules
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+!.env.example
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# contracts build artifacts and dependencies
+contracts/cache
+contracts/out
+contracts/dependencies
+
+# prisma
+prisma/dev.db
+prisma/prisma
+
+# test artifacts
+*.png
+test-results/
+playwright-report/
+
+# misc
+.playwright-mcp/
+sybil-airdrop/
diff --git a/.playwright-cli/console-2026-03-10T03-29-27-605Z.log b/.playwright-cli/console-2026-03-10T03-29-27-605Z.log
new file mode 100644
index 0000000..5a2cdbe
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T03-29-27-605Z.log
@@ -0,0 +1,71 @@
+[ 297ms] [ERROR] Failed to load resource: the server responded with a status of 500 (Internal Server Error) @ http://localhost:3000/:0
+[ 472ms] Error: ./app/globals.css:924:9
+Parsing css source code failed
+ 922 | }
+ 923 |
+> 924 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
+ | ^
+ 925 |
+ 926 | * {
+ 927 | box-sizing: border-box;
+
+@import rules must precede all rules aside from @charset and @layer statements at [project]/app/globals.css:923:8
+
+Import trace:
+ ./app/globals.css [Client Component Browser]
+ ./app/layout.tsx [Server Component]
+
+
+ at (Error: ./app/globals.css:924:9)
+ at (import rules must precede all rules aside from @charset and @layer statements at [project]/app/globals.css:923:8)
+ at Object.getCompilationErrors (file:///Users/mateosauton/Desktop/MiniApps/agentkit/node_modules/.pnpm/next@15.4.10_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/hot-reloader-turbopack.js:760:59)
+ at DevBundlerService.getCompilationError (file:///Users/mateosauton/Desktop/MiniApps/agentkit/node_modules/.pnpm/next@15.4.10_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/dev-bundler-service.js:39:55)
+ at DevServer.getCompilationError (file:///Users/mateosauton/Desktop/MiniApps/agentkit/node_modules/.pnpm/next@15.4.10_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/next-dev-server.js:686:42)
+ at DevServer.findPageComponents (file:///Users/mateosauton/Desktop/MiniApps/agentkit/node_modules/.pnpm/next@15.4.10_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/next-dev-server.js:656:43)
+ at async DevServer.renderErrorToResponseImpl (file:///Users/mateosauton/Desktop/MiniApps/agentkit/node_modules/.pnpm/next@15.4.10_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-server.js:2614:26)
+[ 466ms] [ERROR] ./app/globals.css:924:9
+Parsing css source code failed
+ 922 | }
+ 923 |
+> 924 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
+ | ^
+ 925 |
+ 926 | * {
+ 927 | box-sizing: border-box;
+
+@import rules must precede all rules aside from @charset and @layer statements at [project]/app/globals.css:923:8
+
+Import trace:
+ ./app/globals.css [Client Component Browser]
+ ./app/layout.tsx [Server Component] @ http://localhost:3000/_next/static/chunks/e570a_next_dist_2a4e67be._.js:1680
+[ 473ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/favicon.ico:0
+[ 537ms] [ERROR] ./app/globals.css:924:9
+Parsing css source code failed
+ 922 | }
+ 923 |
+> 924 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
+ | ^
+ 925 |
+ 926 | * {
+ 927 | box-sizing: border-box;
+
+@import rules must precede all rules aside from @charset and @layer statements at [project]/app/globals.css:923:8
+
+Import trace:
+ ./app/globals.css [Client Component Browser]
+ ./app/layout.tsx [Server Component] @ http://localhost:3000/_next/static/chunks/e570a_next_dist_2a4e67be._.js:1680
+[ 4959ms] [ERROR] ./app/globals.css:924:9
+Parsing css source code failed
+ 922 | }
+ 923 |
+> 924 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
+ | ^
+ 925 |
+ 926 | * {
+ 927 | box-sizing: border-box;
+
+@import rules must precede all rules aside from @charset and @layer statements at [project]/app/globals.css:923:8
+
+Import trace:
+ ./app/globals.css [Client Component Browser]
+ ./app/layout.tsx [Server Component] @ http://localhost:3000/_next/static/chunks/e570a_next_dist_2a4e67be._.js:1680
diff --git a/.playwright-cli/console-2026-03-10T03-29-58-902Z.log b/.playwright-cli/console-2026-03-10T03-29-58-902Z.log
new file mode 100644
index 0000000..f7c08dd
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T03-29-58-902Z.log
@@ -0,0 +1 @@
+[ 1605ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/favicon.ico:0
diff --git a/.playwright-cli/console-2026-03-10T03-30-11-273Z.log b/.playwright-cli/console-2026-03-10T03-30-11-273Z.log
new file mode 100644
index 0000000..c0c40f8
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T03-30-11-273Z.log
@@ -0,0 +1,2 @@
+Total messages: 3 (Errors: 0, Warnings: 0)
+Returning 0 messages for level "error"
diff --git a/.playwright-cli/console-2026-03-10T03-30-32-840Z.log b/.playwright-cli/console-2026-03-10T03-30-32-840Z.log
new file mode 100644
index 0000000..c0c40f8
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T03-30-32-840Z.log
@@ -0,0 +1,2 @@
+Total messages: 3 (Errors: 0, Warnings: 0)
+Returning 0 messages for level "error"
diff --git a/.playwright-cli/console-2026-03-10T03-30-59-545Z.log b/.playwright-cli/console-2026-03-10T03-30-59-545Z.log
new file mode 100644
index 0000000..334ec75
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T03-30-59-545Z.log
@@ -0,0 +1 @@
+[ 928ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/favicon.ico:0
diff --git a/.playwright-cli/console-2026-03-10T03-31-11-883Z.log b/.playwright-cli/console-2026-03-10T03-31-11-883Z.log
new file mode 100644
index 0000000..e8c9d5a
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T03-31-11-883Z.log
@@ -0,0 +1,4 @@
+Total messages: 4 (Errors: 1, Warnings: 0)
+Returning 1 messages for level "error"
+
+[ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/favicon.ico:0
\ No newline at end of file
diff --git a/.playwright-cli/console-2026-03-10T13-34-33-111Z.log b/.playwright-cli/console-2026-03-10T13-34-33-111Z.log
new file mode 100644
index 0000000..bb9fa89
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T13-34-33-111Z.log
@@ -0,0 +1 @@
+[ 1420ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/favicon.ico:0
diff --git a/.playwright-cli/console-2026-03-10T13-35-34-645Z.log b/.playwright-cli/console-2026-03-10T13-35-34-645Z.log
new file mode 100644
index 0000000..14867ec
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T13-35-34-645Z.log
@@ -0,0 +1 @@
+[ 1066ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/register:0
diff --git a/.playwright-cli/console-2026-03-10T13-35-39-116Z.log b/.playwright-cli/console-2026-03-10T13-35-39-116Z.log
new file mode 100644
index 0000000..c8a22f1
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T13-35-39-116Z.log
@@ -0,0 +1 @@
+[ 517ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/status:0
diff --git a/.playwright-cli/console-2026-03-10T13-35-43-282Z.log b/.playwright-cli/console-2026-03-10T13-35-43-282Z.log
new file mode 100644
index 0000000..9fbc716
--- /dev/null
+++ b/.playwright-cli/console-2026-03-10T13-35-43-282Z.log
@@ -0,0 +1 @@
+[ 173ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/agent-challenge:0
diff --git a/.playwright-cli/page-2026-03-10T03-29-28-181Z.yml b/.playwright-cli/page-2026-03-10T03-29-28-181Z.yml
new file mode 100644
index 0000000..e69de29
diff --git a/.playwright-cli/page-2026-03-10T03-29-33-023Z.yml b/.playwright-cli/page-2026-03-10T03-29-33-023Z.yml
new file mode 100644
index 0000000..4a94e21
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-29-33-023Z.yml
@@ -0,0 +1,73 @@
+- generic:
+ - generic [active]:
+ - generic [ref=e5] [cursor=pointer]:
+ - button "Open Next.js Dev Tools" [ref=e6]:
+ - img [ref=e7]
+ - button "Open issues overlay" [ref=e11]:
+ - generic [ref=e12]:
+ - generic [ref=e13]: "0"
+ - generic [ref=e14]: "1"
+ - generic [ref=e15]: Issue
+ - generic [ref=e18]:
+ - generic [ref=e19]:
+ - navigation [ref=e21]:
+ - button "previous" [disabled] [ref=e22]:
+ - img "previous" [ref=e23]
+ - generic [ref=e25]:
+ - generic [ref=e26]: 1/
+ - text: "1"
+ - button "next" [disabled] [ref=e27]:
+ - img "next" [ref=e28]
+ - link "Next.js 15.4.10 (outdated) Turbopack" [ref=e31] [cursor=pointer]:
+ - /url: https://nextjs.org/docs/messages/version-staleness
+ - img [ref=e32]
+ - generic "An outdated version detected (latest is 16.1.6), upgrade is highly recommended!" [ref=e34]: Next.js 15.4.10 (outdated)
+ - generic [ref=e35]: Turbopack
+ - dialog "Build Error" [ref=e37]:
+ - generic [ref=e40]:
+ - generic [ref=e41]:
+ - generic [ref=e42]:
+ - generic [ref=e44]: Build Error
+ - generic [ref=e45]:
+ - button "Copy Stack Trace" [ref=e46] [cursor=pointer]:
+ - img [ref=e47]
+ - button "No related documentation found" [disabled] [ref=e49]:
+ - img [ref=e50]
+ - link "Learn more about enabling Node.js inspector for server code with Chrome DevTools" [ref=e52] [cursor=pointer]:
+ - /url: https://nextjs.org/docs/app/building-your-application/configuring/debugging#server-side-code
+ - img [ref=e53]
+ - paragraph [ref=e62]: Parsing css source code failed
+ - generic [ref=e64]:
+ - generic [ref=e66]:
+ - img [ref=e68]
+ - generic [ref=e70]: ./app/globals.css (924:9)
+ - button "Open in editor" [ref=e71] [cursor=pointer]:
+ - img [ref=e73]
+ - generic [ref=e76]:
+ - generic [ref=e77]: Parsing css source code failed
+ - generic [ref=e78]: 922 |
+ - generic [ref=e79]: "}"
+ - generic [ref=e80]: 923 |
+ - text: ">"
+ - generic [ref=e81]: 924 |
+ - text: "@import"
+ - generic [ref=e82]: url(
+ - link "'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap'" [ref=e84] [cursor=pointer]:
+ - /url: https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap
+ - text: );
+ - generic [ref=e85]: "|"
+ - text: ^
+ - generic [ref=e86]: 925 |
+ - generic [ref=e87]: 926 |
+ - text: "*"
+ - generic [ref=e88]: "{"
+ - generic [ref=e89]: 927 |
+ - generic [ref=e90]: box
+ - text: "-sizing:"
+ - generic [ref=e91]: border
+ - text: "-box;"
+ - generic [ref=e92]: "@import rules must precede all rules aside from @charset and @layer statements at [project]/app/globals.css:923:8 Import trace: ./app/globals.css [Client Component Browser] ./app/layout.tsx [Server Component]"
+ - generic [ref=e93]:
+ - generic [ref=e94]: "1"
+ - generic [ref=e95]: "2"
+ - alert [ref=e96]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-29-53-708Z.yml b/.playwright-cli/page-2026-03-10T03-29-53-708Z.yml
new file mode 100644
index 0000000..58c0ade
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-29-53-708Z.yml
@@ -0,0 +1,61 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - heading "Claim Airdrop" [level=1] [ref=e15]
+ - paragraph [ref=e16]: Prove your agent is backed by a real human and claim your tokens. One claim per human.
+ - 'link "New: Agent Capability Challenge" [ref=e17] [cursor=pointer]':
+ - /url: /agent-challenge
+ - generic [ref=e18]:
+ - heading "Campaign" [level=2] [ref=e19]
+ - generic [ref=e20]:
+ - generic [ref=e21]:
+ - paragraph [ref=e22]: Per claim
+ - paragraph [ref=e23]: 1.0 DROP
+ - generic [ref=e24]:
+ - paragraph [ref=e25]: Network
+ - paragraph [ref=e26]: Base
+ - generic [ref=e27]:
+ - paragraph [ref=e28]: Limit
+ - paragraph [ref=e29]: 1 / human
+ - generic [ref=e30]:
+ - generic [ref=e31]:
+ - heading "Prove & Claim" [level=2] [ref=e32]
+ - paragraph [ref=e33]: Sign a challenge to verify your agent is human-backed
+ - generic [ref=e34]:
+ - button "Connect Wallet" [ref=e35] [cursor=pointer]
+ - paragraph [ref=e36]:
+ - text: Your agent must be
+ - link "registered" [ref=e37] [cursor=pointer]:
+ - /url: /register
+ - text: first.
+ - generic [ref=e38]:
+ - heading "How it works" [level=3] [ref=e39]
+ - generic [ref=e40]:
+ - generic [ref=e41]:
+ - paragraph [ref=e42]: Step 1
+ - paragraph [ref=e43]: Register your agent in the AgentBook with World ID.
+ - generic [ref=e44]:
+ - paragraph [ref=e45]: Step 2
+ - paragraph [ref=e46]: Sign a challenge to prove wallet ownership.
+ - generic [ref=e47]:
+ - paragraph [ref=e48]: Step 3
+ - paragraph [ref=e49]: Claim tokens. The server verifies and sends them.
+ - button "Open Next.js Dev Tools" [ref=e55] [cursor=pointer]:
+ - img [ref=e56]
+ - alert [ref=e59]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-29-56-826Z.yml b/.playwright-cli/page-2026-03-10T03-29-56-826Z.yml
new file mode 100644
index 0000000..58c0ade
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-29-56-826Z.yml
@@ -0,0 +1,61 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - heading "Claim Airdrop" [level=1] [ref=e15]
+ - paragraph [ref=e16]: Prove your agent is backed by a real human and claim your tokens. One claim per human.
+ - 'link "New: Agent Capability Challenge" [ref=e17] [cursor=pointer]':
+ - /url: /agent-challenge
+ - generic [ref=e18]:
+ - heading "Campaign" [level=2] [ref=e19]
+ - generic [ref=e20]:
+ - generic [ref=e21]:
+ - paragraph [ref=e22]: Per claim
+ - paragraph [ref=e23]: 1.0 DROP
+ - generic [ref=e24]:
+ - paragraph [ref=e25]: Network
+ - paragraph [ref=e26]: Base
+ - generic [ref=e27]:
+ - paragraph [ref=e28]: Limit
+ - paragraph [ref=e29]: 1 / human
+ - generic [ref=e30]:
+ - generic [ref=e31]:
+ - heading "Prove & Claim" [level=2] [ref=e32]
+ - paragraph [ref=e33]: Sign a challenge to verify your agent is human-backed
+ - generic [ref=e34]:
+ - button "Connect Wallet" [ref=e35] [cursor=pointer]
+ - paragraph [ref=e36]:
+ - text: Your agent must be
+ - link "registered" [ref=e37] [cursor=pointer]:
+ - /url: /register
+ - text: first.
+ - generic [ref=e38]:
+ - heading "How it works" [level=3] [ref=e39]
+ - generic [ref=e40]:
+ - generic [ref=e41]:
+ - paragraph [ref=e42]: Step 1
+ - paragraph [ref=e43]: Register your agent in the AgentBook with World ID.
+ - generic [ref=e44]:
+ - paragraph [ref=e45]: Step 2
+ - paragraph [ref=e46]: Sign a challenge to prove wallet ownership.
+ - generic [ref=e47]:
+ - paragraph [ref=e48]: Step 3
+ - paragraph [ref=e49]: Claim tokens. The server verifies and sends them.
+ - button "Open Next.js Dev Tools" [ref=e55] [cursor=pointer]:
+ - img [ref=e56]
+ - alert [ref=e59]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-00-533Z.yml b/.playwright-cli/page-2026-03-10T03-30-00-533Z.yml
new file mode 100644
index 0000000..dbafb16
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-00-533Z.yml
@@ -0,0 +1,43 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - heading "Register Your Agent" [level=1] [ref=e15]
+ - paragraph [ref=e16]: Verify your identity with World ID and link your wallet to the AgentBook. This is required before you can claim the airdrop.
+ - generic [ref=e17]:
+ - generic [ref=e18]:
+ - heading "Register Agent" [level=2] [ref=e19]
+ - paragraph [ref=e20]: Link your wallet to a World ID-verified human identity
+ - generic [ref=e21]:
+ - button "Connect Wallet" [ref=e22] [cursor=pointer]
+ - paragraph [ref=e23]: You need a World ID Orb verification to register.
+ - generic [ref=e24]:
+ - heading "What happens" [level=3] [ref=e25]
+ - generic [ref=e26]:
+ - generic [ref=e27]:
+ - generic [ref=e28]: "1"
+ - paragraph [ref=e29]: Connect your wallet to identify the agent address.
+ - generic [ref=e30]:
+ - generic [ref=e31]: "2"
+ - paragraph [ref=e32]: Verify with World ID using your Orb-verified identity.
+ - generic [ref=e33]:
+ - generic [ref=e34]: "3"
+ - paragraph [ref=e35]: Register on-chain. Your wallet is linked to your anonymous human ID.
+ - button "Open Next.js Dev Tools" [ref=e41] [cursor=pointer]:
+ - img [ref=e42]
+ - alert [ref=e45]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-02-095Z.yml b/.playwright-cli/page-2026-03-10T03-30-02-095Z.yml
new file mode 100644
index 0000000..dbafb16
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-02-095Z.yml
@@ -0,0 +1,43 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - heading "Register Your Agent" [level=1] [ref=e15]
+ - paragraph [ref=e16]: Verify your identity with World ID and link your wallet to the AgentBook. This is required before you can claim the airdrop.
+ - generic [ref=e17]:
+ - generic [ref=e18]:
+ - heading "Register Agent" [level=2] [ref=e19]
+ - paragraph [ref=e20]: Link your wallet to a World ID-verified human identity
+ - generic [ref=e21]:
+ - button "Connect Wallet" [ref=e22] [cursor=pointer]
+ - paragraph [ref=e23]: You need a World ID Orb verification to register.
+ - generic [ref=e24]:
+ - heading "What happens" [level=3] [ref=e25]
+ - generic [ref=e26]:
+ - generic [ref=e27]:
+ - generic [ref=e28]: "1"
+ - paragraph [ref=e29]: Connect your wallet to identify the agent address.
+ - generic [ref=e30]:
+ - generic [ref=e31]: "2"
+ - paragraph [ref=e32]: Verify with World ID using your Orb-verified identity.
+ - generic [ref=e33]:
+ - generic [ref=e34]: "3"
+ - paragraph [ref=e35]: Register on-chain. Your wallet is linked to your anonymous human ID.
+ - button "Open Next.js Dev Tools" [ref=e41] [cursor=pointer]:
+ - img [ref=e42]
+ - alert [ref=e45]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-03-484Z.yml b/.playwright-cli/page-2026-03-10T03-30-03-484Z.yml
new file mode 100644
index 0000000..dbafb16
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-03-484Z.yml
@@ -0,0 +1,43 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - heading "Register Your Agent" [level=1] [ref=e15]
+ - paragraph [ref=e16]: Verify your identity with World ID and link your wallet to the AgentBook. This is required before you can claim the airdrop.
+ - generic [ref=e17]:
+ - generic [ref=e18]:
+ - heading "Register Agent" [level=2] [ref=e19]
+ - paragraph [ref=e20]: Link your wallet to a World ID-verified human identity
+ - generic [ref=e21]:
+ - button "Connect Wallet" [ref=e22] [cursor=pointer]
+ - paragraph [ref=e23]: You need a World ID Orb verification to register.
+ - generic [ref=e24]:
+ - heading "What happens" [level=3] [ref=e25]
+ - generic [ref=e26]:
+ - generic [ref=e27]:
+ - generic [ref=e28]: "1"
+ - paragraph [ref=e29]: Connect your wallet to identify the agent address.
+ - generic [ref=e30]:
+ - generic [ref=e31]: "2"
+ - paragraph [ref=e32]: Verify with World ID using your Orb-verified identity.
+ - generic [ref=e33]:
+ - generic [ref=e34]: "3"
+ - paragraph [ref=e35]: Register on-chain. Your wallet is linked to your anonymous human ID.
+ - button "Open Next.js Dev Tools" [ref=e41] [cursor=pointer]:
+ - img [ref=e42]
+ - alert [ref=e45]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-04-656Z.yml b/.playwright-cli/page-2026-03-10T03-30-04-656Z.yml
new file mode 100644
index 0000000..62efe2e
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-04-656Z.yml
@@ -0,0 +1,31 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - heading "Claim Status" [level=1] [ref=e15]
+ - paragraph [ref=e16]: Check if an agent is registered and whether the associated human has claimed.
+ - generic [ref=e17]:
+ - generic [ref=e18]:
+ - heading "Check Status" [level=2] [ref=e19]
+ - paragraph [ref=e20]: Look up registration and claim status by address
+ - generic [ref=e22]:
+ - textbox "0x..." [ref=e23]
+ - button "Check" [ref=e24] [cursor=pointer]
+ - button "Open Next.js Dev Tools" [ref=e30] [cursor=pointer]:
+ - img [ref=e31]
+ - alert [ref=e34]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-06-261Z.yml b/.playwright-cli/page-2026-03-10T03-30-06-261Z.yml
new file mode 100644
index 0000000..62efe2e
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-06-261Z.yml
@@ -0,0 +1,31 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - heading "Claim Status" [level=1] [ref=e15]
+ - paragraph [ref=e16]: Check if an agent is registered and whether the associated human has claimed.
+ - generic [ref=e17]:
+ - generic [ref=e18]:
+ - heading "Check Status" [level=2] [ref=e19]
+ - paragraph [ref=e20]: Look up registration and claim status by address
+ - generic [ref=e22]:
+ - textbox "0x..." [ref=e23]
+ - button "Check" [ref=e24] [cursor=pointer]
+ - button "Open Next.js Dev Tools" [ref=e30] [cursor=pointer]:
+ - img [ref=e31]
+ - alert [ref=e34]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-08-403Z.yml b/.playwright-cli/page-2026-03-10T03-30-08-403Z.yml
new file mode 100644
index 0000000..4b4ed7d
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-08-403Z.yml
@@ -0,0 +1,60 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [disabled] [ref=e29]
+ - button "Run with SDK" [disabled] [ref=e30]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - paragraph [ref=e37]: ">Awaiting challenge start"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: PENDING
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-09-832Z.yml b/.playwright-cli/page-2026-03-10T03-30-09-832Z.yml
new file mode 100644
index 0000000..4b4ed7d
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-09-832Z.yml
@@ -0,0 +1,60 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [disabled] [ref=e29]
+ - button "Run with SDK" [disabled] [ref=e30]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - paragraph [ref=e37]: ">Awaiting challenge start"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: PENDING
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-16-624Z.yml b/.playwright-cli/page-2026-03-10T03-30-16-624Z.yml
new file mode 100644
index 0000000..4b4ed7d
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-16-624Z.yml
@@ -0,0 +1,60 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [disabled] [ref=e29]
+ - button "Run with SDK" [disabled] [ref=e30]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - paragraph [ref=e37]: ">Awaiting challenge start"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: PENDING
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-18-251Z.yml b/.playwright-cli/page-2026-03-10T03-30-18-251Z.yml
new file mode 100644
index 0000000..4b4ed7d
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-18-251Z.yml
@@ -0,0 +1,60 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [disabled] [ref=e29]
+ - button "Run with SDK" [disabled] [ref=e30]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - paragraph [ref=e37]: ">Awaiting challenge start"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: PENDING
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-26-875Z.yml b/.playwright-cli/page-2026-03-10T03-30-26-875Z.yml
new file mode 100644
index 0000000..f177878
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-26-875Z.yml
@@ -0,0 +1,31 @@
+- generic [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [active] [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e66]:
+ - generic [ref=e67]:
+ - heading "Claim Status" [level=1] [ref=e68]
+ - paragraph [ref=e69]: Check if an agent is registered and whether the associated human has claimed.
+ - generic [ref=e70]:
+ - generic [ref=e71]:
+ - heading "Check Status" [level=2] [ref=e72]
+ - paragraph [ref=e73]: Look up registration and claim status by address
+ - generic [ref=e75]:
+ - textbox "0x..." [ref=e76]
+ - button "Check" [ref=e77] [cursor=pointer]
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-28-584Z.yml b/.playwright-cli/page-2026-03-10T03-30-28-584Z.yml
new file mode 100644
index 0000000..f177878
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-28-584Z.yml
@@ -0,0 +1,31 @@
+- generic [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [active] [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e66]:
+ - generic [ref=e67]:
+ - heading "Claim Status" [level=1] [ref=e68]
+ - paragraph [ref=e69]: Check if an agent is registered and whether the associated human has claimed.
+ - generic [ref=e70]:
+ - generic [ref=e71]:
+ - heading "Check Status" [level=2] [ref=e72]
+ - paragraph [ref=e73]: Look up registration and claim status by address
+ - generic [ref=e75]:
+ - textbox "0x..." [ref=e76]
+ - button "Check" [ref=e77] [cursor=pointer]
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-31-264Z.yml b/.playwright-cli/page-2026-03-10T03-30-31-264Z.yml
new file mode 100644
index 0000000..f177878
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-31-264Z.yml
@@ -0,0 +1,31 @@
+- generic [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [active] [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e66]:
+ - generic [ref=e67]:
+ - heading "Claim Status" [level=1] [ref=e68]
+ - paragraph [ref=e69]: Check if an agent is registered and whether the associated human has claimed.
+ - generic [ref=e70]:
+ - generic [ref=e71]:
+ - heading "Check Status" [level=2] [ref=e72]
+ - paragraph [ref=e73]: Look up registration and claim status by address
+ - generic [ref=e75]:
+ - textbox "0x..." [ref=e76]
+ - button "Check" [ref=e77] [cursor=pointer]
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-41-279Z.yml b/.playwright-cli/page-2026-03-10T03-30-41-279Z.yml
new file mode 100644
index 0000000..4b4ed7d
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-41-279Z.yml
@@ -0,0 +1,60 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [disabled] [ref=e29]
+ - button "Run with SDK" [disabled] [ref=e30]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - paragraph [ref=e37]: ">Awaiting challenge start"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: PENDING
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-30-42-633Z.yml b/.playwright-cli/page-2026-03-10T03-30-42-633Z.yml
new file mode 100644
index 0000000..4b4ed7d
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-30-42-633Z.yml
@@ -0,0 +1,60 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [disabled] [ref=e29]
+ - button "Run with SDK" [disabled] [ref=e30]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - paragraph [ref=e37]: ">Awaiting challenge start"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: PENDING
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-31-00-496Z.yml b/.playwright-cli/page-2026-03-10T03-31-00-496Z.yml
new file mode 100644
index 0000000..4b4ed7d
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-31-00-496Z.yml
@@ -0,0 +1,60 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [disabled] [ref=e29]
+ - button "Run with SDK" [disabled] [ref=e30]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - paragraph [ref=e37]: ">Awaiting challenge start"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: PENDING
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-31-04-759Z.yml b/.playwright-cli/page-2026-03-10T03-31-04-759Z.yml
new file mode 100644
index 0000000..4b4ed7d
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-31-04-759Z.yml
@@ -0,0 +1,60 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [disabled] [ref=e29]
+ - button "Run with SDK" [disabled] [ref=e30]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - paragraph [ref=e37]: ">Awaiting challenge start"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: PENDING
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-31-08-424Z.yml b/.playwright-cli/page-2026-03-10T03-31-08-424Z.yml
new file mode 100644
index 0000000..15fcc00
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-31-08-424Z.yml
@@ -0,0 +1,64 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [ref=e29] [cursor=pointer]
+ - button "Run with SDK" [ref=e30] [cursor=pointer]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - generic [ref=e36]:
+ - paragraph [ref=e66]: ">Begin Agent Capability Test"
+ - paragraph [ref=e67]: ">Fetching nonce from /api/agent-challenge"
+ - paragraph [ref=e68]: ">Challenge issued: 2d7bb877..."
+ - paragraph [ref=e69]: ">Nonce: 7ec0837c71727db823a0fcaa498c824a"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: OK
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T03-31-10-440Z.yml b/.playwright-cli/page-2026-03-10T03-31-10-440Z.yml
new file mode 100644
index 0000000..15fcc00
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T03-31-10-440Z.yml
@@ -0,0 +1,64 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - navigation [ref=e3]:
+ - generic [ref=e4]:
+ - link "Dropping Air" [ref=e5] [cursor=pointer]:
+ - /url: /
+ - generic [ref=e6]: Base
+ - generic [ref=e7]:
+ - link "Claim" [ref=e8] [cursor=pointer]:
+ - /url: /
+ - link "Register" [ref=e9] [cursor=pointer]:
+ - /url: /register
+ - link "Status" [ref=e10] [cursor=pointer]:
+ - /url: /status
+ - link "Agent Test" [ref=e11] [cursor=pointer]:
+ - /url: /agent-challenge
+ - main [ref=e12]:
+ - generic [ref=e13]:
+ - generic [ref=e14]:
+ - paragraph [ref=e15]: Inverted CAPTCHA
+ - heading "Agent Capability Challenge" [level=1] [ref=e16]
+ - paragraph [ref=e17]: Easy for software agents and intentionally awkward for humans. Complete protocol-level tasks to unlock claiming.
+ - generic [ref=e18]:
+ - heading "Flow" [level=2] [ref=e19]
+ - list [ref=e20]:
+ - listitem [ref=e21]: Fetch nonce from endpoint
+ - listitem [ref=e22]: Sign structured payload
+ - listitem [ref=e23]: Return deterministic JSON schema
+ - listitem [ref=e24]: Claim appears only after success
+ - generic [ref=e25]:
+ - generic [ref=e26]:
+ - button "Begin Agent Capability Test" [ref=e27] [cursor=pointer]
+ - generic [ref=e28]:
+ - button "Copy API challenge" [ref=e29] [cursor=pointer]
+ - button "Run with SDK" [ref=e30] [cursor=pointer]
+ - generic [ref=e31]:
+ - paragraph [ref=e33]: agent-capability-terminal
+ - generic [ref=e35]:
+ - generic [ref=e36]:
+ - paragraph [ref=e66]: ">Begin Agent Capability Test"
+ - paragraph [ref=e67]: ">Fetching nonce from /api/agent-challenge"
+ - paragraph [ref=e68]: ">Challenge issued: 2d7bb877..."
+ - paragraph [ref=e69]: ">Nonce: 7ec0837c71727db823a0fcaa498c824a"
+ - generic [ref=e38]:
+ - generic [ref=e39]:
+ - generic [ref=e40]: Challenge issued
+ - generic [ref=e41]: OK
+ - generic [ref=e42]:
+ - generic [ref=e43]: Proof received
+ - generic [ref=e44]: PENDING
+ - generic [ref=e45]:
+ - generic [ref=e46]: World ID delegation matched
+ - generic [ref=e47]: PENDING
+ - generic [ref=e48]: Claim remains locked.
+ - generic [ref=e49]:
+ - generic [ref=e50]:
+ - heading "Pros" [level=3] [ref=e51]
+ - paragraph [ref=e52]: Strong agent-first UX that feels native to bot developers.
+ - generic [ref=e53]:
+ - heading "Cons" [level=3] [ref=e54]
+ - paragraph [ref=e55]: Human power-users may still automate and pass unless backend anti-abuse logic is robust.
+ - button "Open Next.js Dev Tools" [ref=e61] [cursor=pointer]:
+ - img [ref=e62]
+ - alert [ref=e65]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-34-34-559Z.yml b/.playwright-cli/page-2026-03-10T13-34-34-559Z.yml
new file mode 100644
index 0000000..8f9562b
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-34-34-559Z.yml
@@ -0,0 +1,14 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e11]:
+ - paragraph [ref=e12]: ALERT! This is|
+ - paragraph [ref=e14]: "> System idle. Awaiting robot authentication."
+ - button "Open Next.js Dev Tools" [ref=e20] [cursor=pointer]:
+ - img [ref=e21]
+ - alert [ref=e24]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-34-38-062Z.yml b/.playwright-cli/page-2026-03-10T13-34-38-062Z.yml
new file mode 100644
index 0000000..ac1a1ad
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-34-38-062Z.yml
@@ -0,0 +1,15 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e11]:
+ - paragraph [ref=e12]: ALERT! This is a agent only website, no humans allowed
+ - button "ARE YOU A ROBOT?" [ref=e25] [cursor=pointer]
+ - paragraph [ref=e14]: "> System idle. Awaiting robot authentication."
+ - button "Open Next.js Dev Tools" [ref=e20] [cursor=pointer]:
+ - img [ref=e21]
+ - alert [ref=e24]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-34-54-138Z.yml b/.playwright-cli/page-2026-03-10T13-34-54-138Z.yml
new file mode 100644
index 0000000..b6effbe
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-34-54-138Z.yml
@@ -0,0 +1,23 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e11]:
+ - paragraph [ref=e12]: ALERT! This is a agent only website, no humans allowed
+ - button "ROBOT CONFIRMED" [disabled] [ref=e26]
+ - generic [ref=e28]:
+ - paragraph [ref=e29]: Onboarding
+ - paragraph [ref=e30]: "Step 1: Confirm robot identity and initiate agent registration."
+ - generic [ref=e31]:
+ - button "Skip" [ref=e32] [cursor=pointer]
+ - button "Next" [ref=e33] [cursor=pointer]
+ - generic [ref=e13]:
+ - paragraph [ref=e14]: "> System idle. Awaiting robot authentication."
+ - paragraph [ref=e34]: "> Robot handshake accepted."
+ - button "Open Next.js Dev Tools" [ref=e20] [cursor=pointer]:
+ - img [ref=e21]
+ - alert [ref=e24]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-34-55-388Z.yml b/.playwright-cli/page-2026-03-10T13-34-55-388Z.yml
new file mode 100644
index 0000000..b6effbe
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-34-55-388Z.yml
@@ -0,0 +1,23 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e11]:
+ - paragraph [ref=e12]: ALERT! This is a agent only website, no humans allowed
+ - button "ROBOT CONFIRMED" [disabled] [ref=e26]
+ - generic [ref=e28]:
+ - paragraph [ref=e29]: Onboarding
+ - paragraph [ref=e30]: "Step 1: Confirm robot identity and initiate agent registration."
+ - generic [ref=e31]:
+ - button "Skip" [ref=e32] [cursor=pointer]
+ - button "Next" [ref=e33] [cursor=pointer]
+ - generic [ref=e13]:
+ - paragraph [ref=e14]: "> System idle. Awaiting robot authentication."
+ - paragraph [ref=e34]: "> Robot handshake accepted."
+ - button "Open Next.js Dev Tools" [ref=e20] [cursor=pointer]:
+ - img [ref=e21]
+ - alert [ref=e24]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-35-12-478Z.yml b/.playwright-cli/page-2026-03-10T13-35-12-478Z.yml
new file mode 100644
index 0000000..b6effbe
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-35-12-478Z.yml
@@ -0,0 +1,23 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e11]:
+ - paragraph [ref=e12]: ALERT! This is a agent only website, no humans allowed
+ - button "ROBOT CONFIRMED" [disabled] [ref=e26]
+ - generic [ref=e28]:
+ - paragraph [ref=e29]: Onboarding
+ - paragraph [ref=e30]: "Step 1: Confirm robot identity and initiate agent registration."
+ - generic [ref=e31]:
+ - button "Skip" [ref=e32] [cursor=pointer]
+ - button "Next" [ref=e33] [cursor=pointer]
+ - generic [ref=e13]:
+ - paragraph [ref=e14]: "> System idle. Awaiting robot authentication."
+ - paragraph [ref=e34]: "> Robot handshake accepted."
+ - button "Open Next.js Dev Tools" [ref=e20] [cursor=pointer]:
+ - img [ref=e21]
+ - alert [ref=e24]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-35-22-064Z.yml b/.playwright-cli/page-2026-03-10T13-35-22-064Z.yml
new file mode 100644
index 0000000..6b4e974
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-35-22-064Z.yml
@@ -0,0 +1,21 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e11]:
+ - paragraph [ref=e12]: ALERT! This is a agent only website, no humans allowed
+ - button "ROBOT CONFIRMED" [disabled] [ref=e26]
+ - generic [ref=e35]:
+ - paragraph [ref=e36]: Agent Registration Stage
+ - button "Connect Wallet" [ref=e37] [cursor=pointer]
+ - generic [ref=e13]:
+ - paragraph [ref=e14]: "> System idle. Awaiting robot authentication."
+ - paragraph [ref=e34]: "> Robot handshake accepted."
+ - paragraph [ref=e38]: "> Onboarding skipped. Starting registration flow."
+ - button "Open Next.js Dev Tools" [ref=e20] [cursor=pointer]:
+ - img [ref=e21]
+ - alert [ref=e24]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-35-36-031Z.yml b/.playwright-cli/page-2026-03-10T13-35-36-031Z.yml
new file mode 100644
index 0000000..0d9328a
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-35-36-031Z.yml
@@ -0,0 +1,14 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e10]:
+ - heading "404" [level=1] [ref=e11]
+ - heading "This page could not be found." [level=2] [ref=e13]
+ - button "Open Next.js Dev Tools" [ref=e19] [cursor=pointer]:
+ - img [ref=e20]
+ - alert [ref=e23]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-35-37-771Z.yml b/.playwright-cli/page-2026-03-10T13-35-37-771Z.yml
new file mode 100644
index 0000000..0d9328a
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-35-37-771Z.yml
@@ -0,0 +1,14 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e10]:
+ - heading "404" [level=1] [ref=e11]
+ - heading "This page could not be found." [level=2] [ref=e13]
+ - button "Open Next.js Dev Tools" [ref=e19] [cursor=pointer]:
+ - img [ref=e20]
+ - alert [ref=e23]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-35-39-964Z.yml b/.playwright-cli/page-2026-03-10T13-35-39-964Z.yml
new file mode 100644
index 0000000..0d9328a
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-35-39-964Z.yml
@@ -0,0 +1,14 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e10]:
+ - heading "404" [level=1] [ref=e11]
+ - heading "This page could not be found." [level=2] [ref=e13]
+ - button "Open Next.js Dev Tools" [ref=e19] [cursor=pointer]:
+ - img [ref=e20]
+ - alert [ref=e23]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-35-41-941Z.yml b/.playwright-cli/page-2026-03-10T13-35-41-941Z.yml
new file mode 100644
index 0000000..0d9328a
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-35-41-941Z.yml
@@ -0,0 +1,14 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e10]:
+ - heading "404" [level=1] [ref=e11]
+ - heading "This page could not be found." [level=2] [ref=e13]
+ - button "Open Next.js Dev Tools" [ref=e19] [cursor=pointer]:
+ - img [ref=e20]
+ - alert [ref=e23]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-35-43-593Z.yml b/.playwright-cli/page-2026-03-10T13-35-43-593Z.yml
new file mode 100644
index 0000000..0d9328a
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-35-43-593Z.yml
@@ -0,0 +1,14 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e10]:
+ - heading "404" [level=1] [ref=e11]
+ - heading "This page could not be found." [level=2] [ref=e13]
+ - button "Open Next.js Dev Tools" [ref=e19] [cursor=pointer]:
+ - img [ref=e20]
+ - alert [ref=e23]
\ No newline at end of file
diff --git a/.playwright-cli/page-2026-03-10T13-35-44-917Z.yml b/.playwright-cli/page-2026-03-10T13-35-44-917Z.yml
new file mode 100644
index 0000000..0d9328a
--- /dev/null
+++ b/.playwright-cli/page-2026-03-10T13-35-44-917Z.yml
@@ -0,0 +1,14 @@
+- generic [active] [ref=e1]:
+ - generic [ref=e2]:
+ - banner [ref=e3]:
+ - generic [ref=e4]:
+ - paragraph [ref=e5]: Agent Network
+ - heading "Dropping Air Console" [level=1] [ref=e6]
+ - button "Light mode" [ref=e7] [cursor=pointer]
+ - main [ref=e8]:
+ - generic [ref=e10]:
+ - heading "404" [level=1] [ref=e11]
+ - heading "This page could not be found." [level=2] [ref=e13]
+ - button "Open Next.js Dev Tools" [ref=e19] [cursor=pointer]:
+ - img [ref=e20]
+ - alert [ref=e23]
\ No newline at end of file
diff --git a/.playwright-mcp/console-2026-02-08T03-04-19-153Z.log b/.playwright-mcp/console-2026-02-08T03-04-19-153Z.log
new file mode 100644
index 0000000..9af669c
--- /dev/null
+++ b/.playwright-mcp/console-2026-02-08T03-04-19-153Z.log
@@ -0,0 +1,14 @@
+[ 1732ms] [WARNING] WARN [Statsig] Creating multiple Statsig clients with the same SDK key can lead to unexpected behavior. Multi-instance support requires different SDK keys. @ https://shorthaired-billboard-f9a.notion.site/_assets/65948-bf1a33ee43100a65.js:0
+[ 3008ms] [WARNING] Ignoring inability to install OPFS sqlite3_vfs: Cannot install OPFS: Missing SharedArrayBuffer and/or Atomics. The server must emit the COOP/COEP response headers to enable those. See https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep @ https://shorthaired-billboard-f9a.notion.site/_assets/wasm-sqlite-worker-5cdd36e5fdcb4355.js:0
+[ 3259ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://shorthaired-billboard-f9a.notion.site/api/v3/getBillingData:0
+[ 3259ms] [ERROR] HttpRequestError: Received HTTP 401
+ at https://shorthaired-billboard-f9a.notion.site/_assets/ClientFramework-69b6cb36d2ea6cb1.js:1:66262
+ at async g (https://shorthaired-billboard-f9a.notion.site/_assets/ClientFramework-69b6cb36d2ea6cb1.js:1:65851)
+ at async Object.de [as callApi] (https://shorthaired-billboard-f9a.notion.site/_assets/13369-1acd34c0e1151777.js:183:316674)
+ at async Object.ae [as callCellCompatibleApi] (https://shorthaired-billboard-f9a.notion.site/_assets/13369-1acd34c0e1151777.js:183:315333)
+ at async getValue (https://shorthaired-billboard-f9a.notion.site/_assets/13369-1acd34c0e1151777.js:189:228188)
+ at async s (https://shorthaired-billboard-f9a.notion.site/_assets/ClientFramework-69b6cb36d2ea6cb1.js:1:13865)
+ at async https://shorthaired-billboard-f9a.notion.site/_assets/13369-1acd34c0e1151777.js:189:227976
+ at async i.fetchFn (https://shorthaired-billboard-f9a.notion.site/_assets/13369-1acd34c0e1151777.js:183:236729)
+ at async i.fetch (https://shorthaired-billboard-f9a.notion.site/_assets/ClientFramework-69b6cb36d2ea6cb1.js:1:12163) @ https://shorthaired-billboard-f9a.notion.site/_assets/13369-1acd34c0e1151777.js:182
+[ 3305ms] [WARNING] Amplitude Logger [Warn]: Event not tracked, no destination plugins on the instance @ https://shorthaired-billboard-f9a.notion.site/_assets/transport-support-79187dfcba564918.js:0
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..dd3756a
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+pnpm-lock.yaml
+contracts
+prisma/dev.db
+scripts
+.next
diff --git a/.vercelignore b/.vercelignore
new file mode 100644
index 0000000..b6bcf97
--- /dev/null
+++ b/.vercelignore
@@ -0,0 +1,4 @@
+contracts/
+sybil-airdrop/.next/
+sybil-airdrop/contracts/
+sybil-airdrop/node_modules/
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..49575ce
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,10 @@
+{
+ "recommendations": [
+ "streetsidesoftware.code-spell-checker",
+ "dbaeumer.vscode-eslint",
+ "esbenp.prettier-vscode",
+ "bradlc.vscode-tailwindcss",
+ "csstools.postcss",
+ "richie5um2.vscode-sort-json"
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..cf9ad01
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,9 @@
+{
+ "editor.formatOnSave": true,
+
+ "editor.codeActionsOnSave": {
+ "source.organizeImports": "explicit",
+ "source.fixAll.eslint": "explicit"
+ },
+ "eslint.useFlatConfig": true
+}
diff --git a/@types/env.d.ts b/@types/env.d.ts
new file mode 100644
index 0000000..f9637fc
--- /dev/null
+++ b/@types/env.d.ts
@@ -0,0 +1,19 @@
+declare namespace NodeJS {
+ export interface ProcessEnv {
+ NEXT_PUBLIC_APP_URL: string
+ NODE_ENV: 'development' | 'production' | 'test'
+ NEXT_PUBLIC_APP_ENV: 'development' | 'production' | 'staging'
+
+ // Next Auth
+ NEXT_PUBLIC_WLD_CLIENT_ID: string
+ WLD_CLIENT_SECRET: string
+ NEXTAUTH_SECRET: string
+
+ // Development
+ NEXTAUTH_PROVIDER: 'local' | 'worldcoin' | 'siwe'
+ MOCK_USER_ADDRESS: `0x${string}`
+
+ // Vercel
+ VERCEL_URL: string
+ }
+}
diff --git a/@types/global.d.ts b/@types/global.d.ts
new file mode 100644
index 0000000..d0ff6c1
--- /dev/null
+++ b/@types/global.d.ts
@@ -0,0 +1,26 @@
+interface Window {
+ webkit?: {
+ messageHandlers?: {
+ minikit?: {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ postMessage?: (payload: Record) => void
+ }
+ }
+ }
+
+ Android?: {
+ postMessage?: (payload: string) => void
+ }
+
+ MiniKit?: import('@worldcoin/minikit-js').MiniKit
+
+ WorldApp?: {
+ world_app_version: number
+ device_os: 'ios' | 'android'
+
+ supported_commands: Array<{
+ name: import('@worldcoin/minikit-js').Command
+ supported_versions: Array
+ }>
+ }
+}
diff --git a/@types/next-auth.d.ts b/@types/next-auth.d.ts
new file mode 100644
index 0000000..9641ce6
--- /dev/null
+++ b/@types/next-auth.d.ts
@@ -0,0 +1,12 @@
+import 'next-auth'
+import 'next-auth/jwt'
+
+declare module 'next-auth' {
+ interface User {
+ id: string
+ }
+
+ interface Session {
+ user: User
+ }
+}
diff --git a/@types/next-intl.d.ts b/@types/next-intl.d.ts
new file mode 100644
index 0000000..8376567
--- /dev/null
+++ b/@types/next-intl.d.ts
@@ -0,0 +1,9 @@
+import { Locale } from '@/i18n/routing'
+import en from '../messages/en.json'
+
+declare module 'next-intl' {
+ interface AppConfig {
+ Locale: Locale
+ Messages: typeof en
+ }
+}
diff --git a/QA_TEST_PLAN.md b/QA_TEST_PLAN.md
new file mode 100644
index 0000000..da3d06a
--- /dev/null
+++ b/QA_TEST_PLAN.md
@@ -0,0 +1,29 @@
+# QA Test Plan
+
+This is the living QA plan for this repository. Update it on every task execution and when merged changes introduce or modify behavior.
+
+## Active Checklist
+
+| ID | Area | Scenario | Expected | Run in this task | Last result |
+| --- | --- | --- | --- | --- | --- |
+| QA-001 | Home page load | Open `http://localhost:3000` in browser | Page renders without runtime error | yes | pass |
+| QA-002 | Matrix onboarding | Wait for intro typing to finish and observe CTA | `ARE YOU A ROBOT?` button appears only after alert text is typed | yes | pass |
+| QA-003 | Robot gate state | Click `ARE YOU A ROBOT?`, finish/skip onboarding, then continue in same session | Onboarding modal does not appear again unless page refresh | yes | pass |
+| QA-004 | Agent verification stage | After onboarding, connect wallet and run verification challenge | Verification reaches success state only when signed proof validates and AgentBook lookup succeeds | no | not-run |
+| QA-005 | Typing challenge controls | Start challenge, try to paste text into input, and type words | Paste is blocked, words are shown in batches of 10, progress/time counters update | no | not-run |
+| QA-006 | Typing challenge success gate | Complete all 365 words under 60s | Claim button transitions from locked to enabled | no | not-run |
+| QA-007 | Claim submission | With verified agent + passed typing challenge, click claim | Claim request succeeds and transaction link appears | no | not-run |
+| QA-008 | Removed routes hard check | Open `/register`, `/status`, and `/agent-challenge` | Routes are no longer available in root app (404/not found) | yes | pass |
+| QA-009 | Skills documentation | Verify workflow skill files exist under `.cursor/skills/` | Skill files are present and readable | yes | pass |
+| QA-010 | API request correlation | Call `GET /api/challenge` from local app | Response includes `x-request-id` header and `requestId` JSON field with matching values | yes | pass |
+| QA-011 | API error taxonomy | Trigger a failing challenge request in local env | Error response includes safe `error` text, stable `code`, and `requestId` | yes | pass |
+| QA-012 | Onboarding interaction regression | Open home page, wait for CTA, click `ARE YOU A ROBOT?` | Onboarding modal appears with `Step 1` and CTA switches to `ROBOT CONFIRMED` | yes | pass |
+| QA-013 | Observability docs | Open `docs/OBSERVABILITY_RUNBOOK.md` | Runbook lists key events, dashboards, alert thresholds, and triage workflow | yes | pass |
+| QA-014 | Local dev startup | Run `pnpm dev` from repo root and load app URL | Next.js dev server boots successfully and responds with `200 OK` (uses next open port if `3000` is busy) | yes | pass |
+| QA-015 | Preview deployment | Run `vercel deploy -y` from repo root | Vercel preview build completes and returns a reachable preview URL | yes | pass |
+
+## Update Rules
+
+- Add at least one new item for each behavior change.
+- Mark each item run result as `pass`, `fail`, or `not-run`.
+- Remove or update obsolete items when behavior is removed.
diff --git a/README.md b/README.md
index c9ef37b..c1af77b 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,41 @@
-# Agent Kit
+### How to start
-Verify that an agent is backed by a real, World ID-verified human.
+1. Create a repo from this template.
+2. Clone the repo to your local machine.
+3. Run `pnpm install` to install the dependencies.
+4. Run `pnpm dev` to start the development server.
-Agent Kit is an x402 extension for websites and APIs that want to recognize human-backed agents. It combines:
+### Auth
-- `@worldcoin/agentkit-cli` for registering an agent wallet in AgentBook with a World ID proof
-- `@worldcoin/agentkit` for integrating AgentBook-backed verification into x402 server flows
+- For the authenticaton we are using Sign In with Worldcoin configured via next-auth. You can find the configuration in `pages/api/auth/[...nextauth].js`.
+- To make auth work you will need to set these envs:
+ - `WLD_CLIENT_ID`. Go to the Developer Portal and create a new app. You will get the client id there. Select an app -> Sign in with World ID tab -> copy the client id.
+ - `WLD_CLIENT_SECRET`. Go to the Developer Portal use the same app as for the WLD_CLIENT_ID. Select an app -> Sign in with World ID tab -> Client secret input -> If you doing this for the first time you will need to press "Reset" button to generate the secret.
+ - `NEXTAUTH_SECRET`. This is a random string that is used to encrypt the session token. You can generate it with `openssl rand -base64 32` or use any other method to generate a random string. ([Source](https://next-auth.js.org/configuration/options#description-1))
+- Set a redirect URL in the Developer Portal. Go to the Developer Portal -> Same app -> Sign in with World ID tab -> Redirects section -> Add `http://localhost:3000/api/auth/callback/worldcoin` for local development. For production, replace `http://localhost:3000` with your domain.
-Registration does not have to require the agent to hold gas. `agentkit register ` now defaults to Base mainnet plus the shared hosted relay at `https://x402-worldchain.vercel.app`, and `API_URL` can override it.
+### MiniKit
-For the common production setup, keep payments on World Chain and keep AgentBook lookup on Base mainnet.
+
-## Default Registration Flow
+- Already installed and ready to use. You can find the documentation here: [MiniKit]()
-Start with this prompt:
+
-```text
-Run `npx @worldcoin/agentkit-cli --llms`, then help me register your wallet address in the AgentBook.
-```
+- To test you need to...
-Use the CLI's machine-readable guidance instead of reconstructing the flow from repo docs.
+### Optional
-## What It Does
+- `.vscode/`: Visual Studio Code settings
+ - If you don't use Visual Studio Code, you can delete this folder.
+ - VSC settings include auto-formatting and lint fix on file save. You can disable this by adjusting the settings in `.vscode/settings.json`.
+- `app/api/verify-siwe`: This is an API route that can be used to verify the Sign-In with Ethereum. You can delete this folder if you don't need SIWE.
+- `scenes/Home/MiniKitDemoClient`: This is a component that has a set of test fields. To test it you will to launch your mini app inside WorldApp. Can be deleted if you don't need it or if you already tested it.
-1. An agent wallet is registered in AgentBook using a World ID proof.
-2. A website or API using x402 challenges the agent to sign a CAIP-122 message.
-3. The server verifies the signature, resolves the registering human from AgentBook, and applies the configured access policy.
+### Environment Variables
-This lets applications distinguish between arbitrary automation and automation acting on behalf of a real human, without exposing the human's underlying identity.
+- To keep ENVs in sync with TypeScript types, you can use the `env.d.ts` file. This file is used to define the types of the ENVs that are used in the app. If you add a new ENV, make sure to add it to the `Env` type in `env.d.ts`.
-## Docs
+### Observability
-- Registration: [`./cli/REGISTRATION.md`](./cli/REGISTRATION.md)
-- Server integration: [`./agentkit/DOCS.md`](./agentkit/DOCS.md) for the end-to-end x402 flow, including World Chain payments plus Base AgentBook lookup
+- Operational logging, tracing, alerts, and incident triage are documented in `docs/OBSERVABILITY_RUNBOOK.md`.
diff --git a/app/api/agent-challenge/route.ts b/app/api/agent-challenge/route.ts
new file mode 100644
index 0000000..11a4682
--- /dev/null
+++ b/app/api/agent-challenge/route.ts
@@ -0,0 +1,238 @@
+import { randomBytes, randomUUID } from 'crypto'
+import { NextRequest } from 'next/server'
+import { recoverMessageAddress } from 'viem'
+import { z } from 'zod'
+import { agentBook } from '@/lib/agentkit'
+import { env } from '@/lib/config'
+import {
+ createRequestContext,
+ elapsedMs,
+ ERROR_CODES,
+ logEvent,
+ maskAddress,
+ responseError,
+ responseWithRequestId,
+ withSpan,
+} from '@/lib/observability'
+
+type ChallengeRecord = {
+ nonce: string
+ issuedAt: string
+ expiresAt: string
+ used: boolean
+}
+
+const ACTIVE_CHALLENGES = new Map()
+const CHALLENGE_TTL_MS = 5 * 60 * 1000
+
+const proofSchema = z.object({
+ challengeId: z.string().min(1),
+ proof: z.object({
+ address: z.string().startsWith('0x'),
+ nonce: z.string().min(1),
+ signature: z.string().startsWith('0x'),
+ delegation: z.object({
+ worldIdSignal: z.string().startsWith('0x'),
+ }),
+ payload: z.object({
+ challengeId: z.string().min(1),
+ nonce: z.string().min(1),
+ capability: z.literal('agent-capability-test'),
+ timestamp: z.string().datetime(),
+ sdk: z.literal('agentkit'),
+ schemaVersion: z.literal('1.0.0'),
+ }),
+ }),
+})
+
+function canonicalizePayload(payload: {
+ challengeId: string
+ nonce: string
+ capability: string
+ timestamp: string
+ sdk: string
+ schemaVersion: string
+}) {
+ return JSON.stringify({
+ capability: payload.capability,
+ challengeId: payload.challengeId,
+ nonce: payload.nonce,
+ schemaVersion: payload.schemaVersion,
+ sdk: payload.sdk,
+ timestamp: payload.timestamp,
+ })
+}
+
+function trimExpiredChallenges() {
+ const now = Date.now()
+ for (const [challengeId, record] of ACTIVE_CHALLENGES) {
+ if (new Date(record.expiresAt).getTime() < now) {
+ ACTIVE_CHALLENGES.delete(challengeId)
+ }
+ }
+}
+
+export async function GET(req: NextRequest) {
+ const ctx = createRequestContext(req, '/api/agent-challenge')
+ trimExpiredChallenges()
+ logEvent('info', 'agent_challenge.issue.start', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ })
+
+ const challengeId = randomUUID()
+ const nonce = randomBytes(16).toString('hex')
+ const issuedAt = new Date().toISOString()
+ const expiresAt = new Date(Date.now() + CHALLENGE_TTL_MS).toISOString()
+
+ ACTIVE_CHALLENGES.set(challengeId, {
+ nonce,
+ issuedAt,
+ expiresAt,
+ used: false,
+ })
+
+ logEvent('info', 'agent_challenge.issue.success', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ durationMs: elapsedMs(ctx.startedAt),
+ })
+
+ return responseWithRequestId(ctx, {
+ challengeId,
+ nonce,
+ issuedAt,
+ expiresAt,
+ tasks: [
+ 'fetch nonce from endpoint',
+ 'sign structured payload',
+ 'return deterministic JSON schema',
+ ],
+ payloadTemplate: {
+ capability: 'agent-capability-test',
+ challengeId,
+ nonce,
+ schemaVersion: '1.0.0',
+ sdk: 'agentkit',
+ timestamp: issuedAt,
+ },
+ })
+}
+
+export async function POST(req: NextRequest) {
+ const ctx = createRequestContext(req, '/api/agent-challenge')
+ trimExpiredChallenges()
+ logEvent('info', 'agent_challenge.verify.start', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ })
+
+ try {
+ const json = await req.json()
+ const parsed = proofSchema.safeParse(json)
+ if (!parsed.success) {
+ return responseError(ctx, 400, ERROR_CODES.BAD_REQUEST, 'Invalid proof payload')
+ }
+
+ const { challengeId, proof } = parsed.data
+ const challenge = ACTIVE_CHALLENGES.get(challengeId)
+ if (!challenge) {
+ return responseError(ctx, 404, ERROR_CODES.CHALLENGE_NOT_FOUND, 'Unknown challenge')
+ }
+ if (challenge.used) {
+ return responseError(ctx, 409, ERROR_CODES.CHALLENGE_USED, 'Challenge already used')
+ }
+ if (new Date(challenge.expiresAt).getTime() < Date.now()) {
+ return responseError(ctx, 410, ERROR_CODES.NONCE_EXPIRED, 'Challenge expired')
+ }
+
+ const expectedPayload = canonicalizePayload({
+ capability: 'agent-capability-test',
+ challengeId,
+ nonce: challenge.nonce,
+ schemaVersion: '1.0.0',
+ sdk: 'agentkit',
+ timestamp: challenge.issuedAt,
+ })
+ const receivedPayload = canonicalizePayload(proof.payload)
+ if (receivedPayload !== expectedPayload) {
+ return responseError(
+ ctx,
+ 400,
+ ERROR_CODES.DETERMINISTIC_PAYLOAD_MISMATCH,
+ 'Deterministic schema mismatch'
+ )
+ }
+ if (proof.nonce !== challenge.nonce || proof.payload.nonce !== challenge.nonce) {
+ return responseError(ctx, 400, ERROR_CODES.NONCE_INVALID, 'Nonce mismatch')
+ }
+ if (proof.payload.challengeId !== challengeId) {
+ return responseError(ctx, 400, ERROR_CODES.BAD_REQUEST, 'Challenge ID mismatch')
+ }
+
+ const message = `agent-capability:${expectedPayload}`
+ const recoveredAddress = await withSpan(
+ 'agent_challenge.recover_address',
+ {
+ route: ctx.route,
+ },
+ () =>
+ recoverMessageAddress({
+ message,
+ signature: proof.signature as `0x${string}`,
+ })
+ )
+
+ if (recoveredAddress.toLowerCase() !== proof.address.toLowerCase()) {
+ return responseError(ctx, 401, ERROR_CODES.INVALID_SIGNATURE, 'Signature check failed')
+ }
+ if (proof.delegation.worldIdSignal.toLowerCase() !== proof.address.toLowerCase()) {
+ return responseError(ctx, 403, ERROR_CODES.INVALID_SIGNATURE, 'World ID delegation mismatch')
+ }
+ const humanId = await withSpan(
+ 'agent_challenge.lookup_human',
+ {
+ route: ctx.route,
+ },
+ () => agentBook.lookupHuman(proof.address, env.NEXT_PUBLIC_CHAIN_ID)
+ )
+ if (!humanId) {
+ return responseError(
+ ctx,
+ 403,
+ ERROR_CODES.AGENT_NOT_REGISTERED,
+ 'Agent is not registered in the AgentBook. Register first.'
+ )
+ }
+
+ challenge.used = true
+ ACTIVE_CHALLENGES.set(challengeId, challenge)
+
+ logEvent('info', 'agent_challenge.verify.success', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ wallet: maskAddress(proof.address),
+ durationMs: elapsedMs(ctx.startedAt),
+ })
+
+ return responseWithRequestId(ctx, {
+ success: true,
+ status: {
+ challengeIssued: true,
+ proofReceived: true,
+ worldIdDelegationMatched: true,
+ },
+ claimUnlocked: true,
+ humanId,
+ message,
+ })
+ } catch (error) {
+ logEvent('error', 'agent_challenge.verify.failed', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ durationMs: elapsedMs(ctx.startedAt),
+ errorMessage: error instanceof Error ? error.message : 'unknown',
+ })
+ return responseError(ctx, 500, ERROR_CODES.INTERNAL_ERROR, 'Challenge verification failed')
+ }
+}
diff --git a/app/api/challenge/route.ts b/app/api/challenge/route.ts
new file mode 100644
index 0000000..eee9df6
--- /dev/null
+++ b/app/api/challenge/route.ts
@@ -0,0 +1,80 @@
+import { NextRequest } from 'next/server'
+import { generateNonce } from 'siwe'
+import db from '@/lib/db'
+import { env } from '@/lib/config'
+import {
+ createRequestContext,
+ elapsedMs,
+ ERROR_CODES,
+ logEvent,
+ responseError,
+ responseWithRequestId,
+ withSpan,
+} from '@/lib/observability'
+
+export async function GET(req: NextRequest) {
+ const ctx = createRequestContext(req, '/api/challenge')
+ logEvent('info', 'challenge.request.start', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ method: ctx.method,
+ })
+
+ try {
+ const nonce = generateNonce()
+ const domain = env.NEXT_PUBLIC_DOMAIN
+ const chainId = env.NEXT_PUBLIC_CHAIN_ID
+ const uri = `https://${domain}`
+ const now = new Date()
+ const expiresAt = new Date(now.getTime() + 5 * 60 * 1000)
+
+ const { error } = await withSpan(
+ 'challenge.insert',
+ {
+ route: ctx.route,
+ },
+ async () =>
+ db.from('Challenge').insert({
+ nonce,
+ domain,
+ uri,
+ chainId,
+ expiresAt: expiresAt.toISOString(),
+ })
+ )
+
+ if (error) {
+ logEvent('error', 'challenge.insert.failed', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ durationMs: elapsedMs(ctx.startedAt),
+ errorMessage: error.message,
+ })
+ return responseError(ctx, 500, ERROR_CODES.DB_WRITE_FAILED, 'Failed to create challenge')
+ }
+
+ logEvent('info', 'challenge.request.success', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ durationMs: elapsedMs(ctx.startedAt),
+ })
+
+ return responseWithRequestId(ctx, {
+ nonce,
+ domain,
+ uri,
+ chainId,
+ issuedAt: now.toISOString(),
+ expirationTime: expiresAt.toISOString(),
+ supportedChains: [chainId],
+ })
+ } catch (error) {
+ logEvent('error', 'challenge.request.failed', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ durationMs: elapsedMs(ctx.startedAt),
+ errorMessage: error instanceof Error ? error.message : 'unknown',
+ })
+ return responseError(ctx, 500, ERROR_CODES.INTERNAL_ERROR, 'Internal server error')
+ }
+}
diff --git a/app/api/claim/route.ts b/app/api/claim/route.ts
new file mode 100644
index 0000000..6ac99db
--- /dev/null
+++ b/app/api/claim/route.ts
@@ -0,0 +1,210 @@
+import { NextRequest } from 'next/server'
+import {
+ parseAgentkitHeader,
+ validateAgentkitMessage,
+ verifyAgentkitSignature,
+ agentBook,
+} from '@/lib/agentkit'
+import { executeAirdrop } from '@/lib/airdrop'
+import db from '@/lib/db'
+import type { Challenge, Claim } from '@/lib/db'
+import { env } from '@/lib/config'
+import {
+ createRequestContext,
+ elapsedMs,
+ ERROR_CODES,
+ logEvent,
+ maskAddress,
+ responseError,
+ responseWithRequestId,
+ withSpan,
+} from '@/lib/observability'
+
+export async function POST(req: NextRequest) {
+ const ctx = createRequestContext(req, '/api/claim')
+ logEvent('info', 'claim.request.start', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ method: ctx.method,
+ })
+
+ try {
+ const { payload } = await req.json()
+
+ if (!payload || typeof payload !== 'string') {
+ return responseError(ctx, 400, ERROR_CODES.BAD_REQUEST, 'Missing or invalid payload')
+ }
+
+ let parsed
+ try {
+ parsed = parseAgentkitHeader(payload)
+ } catch (err) {
+ logEvent('warn', 'claim.payload.parse_failed', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ errorMessage: err instanceof Error ? err.message : 'unknown',
+ })
+ return responseError(ctx, 400, ERROR_CODES.BAD_REQUEST, 'Failed to parse payload')
+ }
+
+ const resourceUri = `https://${env.NEXT_PUBLIC_DOMAIN}`
+ const validation = await withSpan(
+ 'claim.validate_agentkit_message',
+ {
+ route: ctx.route,
+ },
+ () =>
+ validateAgentkitMessage(parsed, resourceUri, {
+ checkNonce: async (nonce: string) => {
+ const { data: challenge } = await db
+ .from('Challenge')
+ .select('*')
+ .eq('nonce', nonce)
+ .single()
+
+ if (!challenge || challenge.used || new Date(challenge.expiresAt) < new Date()) {
+ return false
+ }
+
+ await db.from('Challenge').update({ used: true }).eq('nonce', nonce)
+ return true
+ },
+ })
+ )
+
+ if (!validation.valid) {
+ logEvent('warn', 'claim.validation.failed', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ validationError: validation.error,
+ })
+ return responseError(ctx, 400, ERROR_CODES.NONCE_INVALID, 'Validation failed')
+ }
+
+ const verification = await withSpan(
+ 'claim.verify_signature',
+ {
+ route: ctx.route,
+ },
+ () => verifyAgentkitSignature(parsed)
+ )
+ if (!verification.valid || !verification.address) {
+ logEvent('warn', 'claim.signature.invalid', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ verificationError: verification.error,
+ })
+ return responseError(ctx, 401, ERROR_CODES.INVALID_SIGNATURE, 'Signature verification failed')
+ }
+
+ const agentAddress = verification.address as `0x${string}`
+
+ const nullifierHash = await withSpan(
+ 'claim.lookup_human',
+ {
+ route: ctx.route,
+ },
+ () => agentBook.lookupHuman(agentAddress, env.NEXT_PUBLIC_CHAIN_ID)
+ )
+ if (!nullifierHash) {
+ logEvent('warn', 'claim.agent_not_registered', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ wallet: maskAddress(agentAddress),
+ })
+ return responseError(
+ ctx,
+ 403,
+ ERROR_CODES.AGENT_NOT_REGISTERED,
+ 'Agent is not registered in the AgentBook. Register with World ID first.'
+ )
+ }
+
+ const { data: existingClaim } = await withSpan(
+ 'claim.lookup_existing',
+ {
+ route: ctx.route,
+ },
+ async () =>
+ db
+ .from('Claim')
+ .select('*')
+ .eq('nullifierHash', nullifierHash)
+ .single()
+ )
+
+ if (existingClaim) {
+ logEvent('warn', 'claim.already_exists', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ wallet: maskAddress(agentAddress),
+ })
+ return responseWithRequestId(
+ ctx,
+ {
+ error: 'This human has already claimed the airdrop',
+ code: ERROR_CODES.CLAIM_ALREADY_EXISTS,
+ txHash: existingClaim.txHash,
+ claimedAt: existingClaim.claimedAt,
+ },
+ 409
+ )
+ }
+
+ const txHash = await withSpan(
+ 'claim.execute_airdrop',
+ {
+ route: ctx.route,
+ },
+ () => executeAirdrop(agentAddress, BigInt(nullifierHash))
+ )
+
+ const { error: insertError } = await withSpan(
+ 'claim.insert_record',
+ {
+ route: ctx.route,
+ },
+ async () =>
+ db.from('Claim').insert({
+ nullifierHash,
+ agentAddress,
+ txHash,
+ amount: env.CLAIM_AMOUNT,
+ })
+ )
+
+ if (insertError) {
+ logEvent('error', 'claim.insert.failed', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ durationMs: elapsedMs(ctx.startedAt),
+ errorMessage: insertError.message,
+ })
+ return responseError(ctx, 500, ERROR_CODES.DB_WRITE_FAILED, 'Failed to persist claim')
+ }
+
+ logEvent('info', 'claim.request.success', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ wallet: maskAddress(agentAddress),
+ txHash,
+ durationMs: elapsedMs(ctx.startedAt),
+ })
+
+ return responseWithRequestId(ctx, {
+ success: true,
+ txHash,
+ amount: env.CLAIM_AMOUNT,
+ agentAddress,
+ explorerUrl: `https://basescan.org/tx/${txHash}`,
+ })
+ } catch (error) {
+ logEvent('error', 'claim.request.failed', {
+ requestId: ctx.requestId,
+ route: ctx.route,
+ durationMs: elapsedMs(ctx.startedAt),
+ errorMessage: error instanceof Error ? error.message : 'unknown',
+ })
+ return responseError(ctx, 500, ERROR_CODES.INTERNAL_ERROR, 'Internal server error')
+ }
+}
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000..a58b05a
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,59 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+* {
+ box-sizing: border-box;
+}
+
+:root {
+ color-scheme: light;
+}
+
+.dark {
+ color-scheme: dark;
+}
+
+body {
+ background-color: #050b05;
+ color: #d6ffd6;
+ margin: 0;
+ background-image: radial-gradient(circle at top, rgba(34, 197, 94, 0.08), transparent 35%);
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.dark body {
+ background-color: #050b05;
+ color: #d6ffd6;
+}
+
+html:not(.dark) body {
+ background-color: #f3fff3;
+ color: #072b07;
+}
+
+.terminal-panel {
+ border: 1px solid rgba(74, 222, 128, 0.35);
+ background: rgba(0, 0, 0, 0.75);
+ border-radius: 12px;
+ padding: 16px;
+ box-shadow: inset 0 0 24px rgba(34, 197, 94, 0.08);
+}
+
+html:not(.dark) .terminal-panel {
+ background: rgba(255, 255, 255, 0.7);
+ border-color: rgba(34, 197, 94, 0.45);
+}
+
+.matrix-grid {
+ background-image:
+ linear-gradient(to right, rgba(74, 222, 128, 0.14) 1px, transparent 1px),
+ linear-gradient(to bottom, rgba(74, 222, 128, 0.14) 1px, transparent 1px);
+ background-size: 32px 32px;
+}
diff --git a/app/layout.tsx b/app/layout.tsx
new file mode 100644
index 0000000..ef5d98a
--- /dev/null
+++ b/app/layout.tsx
@@ -0,0 +1,22 @@
+import type { Metadata } from 'next'
+import { Analytics } from '@vercel/analytics/next'
+import './globals.css'
+
+export const metadata: Metadata = {
+ title: 'Dropping Air',
+ description: 'Sybil-resistant token airdrop powered by World ID and AgentKit',
+}
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 0000000..89cd9c6
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,9 @@
+import MatrixOnboardingFlow from '@/components/MatrixOnboardingFlow'
+
+export default function ClaimPage() {
+ return (
+
+
+
+ )
+}
diff --git a/components/CampaignInfo.tsx b/components/CampaignInfo.tsx
new file mode 100644
index 0000000..936800b
--- /dev/null
+++ b/components/CampaignInfo.tsx
@@ -0,0 +1,21 @@
+export default function CampaignInfo() {
+ return (
+
+ )
+}
diff --git a/components/ClaimForm.tsx b/components/ClaimForm.tsx
new file mode 100644
index 0000000..865a307
--- /dev/null
+++ b/components/ClaimForm.tsx
@@ -0,0 +1,285 @@
+'use client'
+
+import { useState } from 'react'
+import Link from 'next/link'
+import { createWalletClient, custom } from 'viem'
+import { base } from 'viem/chains'
+import { SiweMessage } from 'siwe'
+import { getResponseRequestId, trackClientEvent } from '@/lib/client-telemetry'
+
+type ClaimState =
+ | { step: 'disconnected' }
+ | { step: 'idle'; address: string }
+ | { step: 'fetching-challenge'; address: string }
+ | { step: 'signing'; address: string }
+ | { step: 'submitting'; address: string }
+ | { step: 'success'; address: string; txHash: string; explorerUrl: string }
+ | { step: 'error'; address: string; message: string }
+
+export default function ClaimForm() {
+ const [state, setState] = useState({ step: 'disconnected' })
+
+ const connect = async () => {
+ const ethereum = (
+ window as unknown as { ethereum?: { request: (...args: unknown[]) => Promise } }
+ ).ethereum
+ if (!ethereum) {
+ alert('No wallet found. Please install MetaMask or another wallet extension.')
+ return
+ }
+ const client = createWalletClient({ chain: base, transport: custom(ethereum) })
+ const [addr] = await client.requestAddresses()
+ setState({ step: 'idle', address: addr })
+ }
+
+ const claim = async () => {
+ if (state.step === 'disconnected') return
+ const { address } = state
+
+ try {
+ trackClientEvent({ event: 'claim_form.start', step: 'challenge-fetch' })
+ setState({ step: 'fetching-challenge', address })
+ const challengeRes = await fetch('/api/challenge')
+ if (!challengeRes.ok) throw new Error('Failed to fetch challenge')
+ const challengeRequestId = getResponseRequestId(challengeRes)
+ const challenge = await challengeRes.json()
+ trackClientEvent({
+ event: 'claim_form.challenge_received',
+ requestId: challengeRequestId,
+ route: '/api/challenge',
+ })
+
+ setState({ step: 'signing', address })
+ const ethereum = (
+ window as unknown as {
+ ethereum?: { request: (...args: unknown[]) => Promise }
+ }
+ ).ethereum
+ const client = createWalletClient({ chain: base, transport: custom(ethereum!) })
+
+ const chainIdNum = parseInt(challenge.chainId.split(':')[1])
+ const message = new SiweMessage({
+ domain: challenge.domain,
+ address,
+ statement: 'Verify your agent is backed by a real human to claim the airdrop',
+ uri: challenge.uri,
+ version: '1',
+ chainId: chainIdNum,
+ nonce: challenge.nonce,
+ issuedAt: challenge.issuedAt,
+ expirationTime: challenge.expirationTime,
+ })
+
+ const signature = await client.signMessage({
+ account: address as `0x${string}`,
+ message: message.prepareMessage(),
+ })
+
+ const payloadData = {
+ domain: message.domain,
+ address: message.address,
+ statement: message.statement,
+ uri: message.uri,
+ version: message.version,
+ chainId: challenge.chainId,
+ nonce: message.nonce,
+ issuedAt: message.issuedAt,
+ expirationTime: message.expirationTime,
+ signature,
+ type: 'eip191' as const,
+ }
+
+ const payload = btoa(JSON.stringify(payloadData))
+
+ setState({ step: 'submitting', address })
+ const res = await fetch('/api/claim', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ payload }),
+ })
+
+ const claimRequestId = getResponseRequestId(res)
+ const data = await res.json()
+ if (!res.ok) throw new Error(data.error || 'Claim failed')
+ trackClientEvent({
+ event: 'claim_form.success',
+ requestId: claimRequestId,
+ route: '/api/claim',
+ })
+
+ setState({
+ step: 'success',
+ address,
+ txHash: data.txHash,
+ explorerUrl: `https://basescan.org/tx/${data.txHash}`,
+ })
+ } catch (e) {
+ trackClientEvent({
+ level: 'error',
+ event: 'claim_form.failed',
+ message: (e as Error).message,
+ })
+ setState({
+ step: 'error',
+ address,
+ message: (e as Error).message,
+ })
+ }
+ }
+
+ const isLoading =
+ state.step === 'fetching-challenge' ||
+ state.step === 'signing' ||
+ state.step === 'submitting'
+
+ return (
+
+
+
Prove & Claim
+
+ Sign a challenge to verify your agent is human-backed
+
+
+
+
+ {state.step !== 'disconnected' && (
+
+ )}
+
+ {state.step !== 'disconnected' && (
+
+ Connected
+
+ {state.address.slice(0, 6)}...{state.address.slice(-4)}
+
+
+ )}
+
+ {state.step === 'success' && (
+
+ )}
+
+ {state.step === 'error' && (
+
+
Failed
+
{state.message}
+
+ )}
+
+ {state.step === 'disconnected' ? (
+
+ Connect Wallet
+
+ ) : (
+
+ {isLoading && (
+
+
+
+
+ )}
+ {state.step === 'idle' && 'Prove Humanity & Claim'}
+ {state.step === 'fetching-challenge' && 'Fetching Challenge...'}
+ {state.step === 'signing' && 'Waiting for Signature...'}
+ {state.step === 'submitting' && 'Submitting...'}
+ {state.step === 'success' && 'Claimed'}
+ {state.step === 'error' && 'Try Again'}
+
+ )}
+
+ {state.step !== 'disconnected' && (
+
+ Try Agent Capability Challenge
+
+ )}
+
+ {state.step === 'disconnected' && (
+
+ Your agent must be{' '}
+
+ registered
+ {' '}
+ first.
+
+ )}
+
+
+ )
+}
+
+function StepIndicator({
+ active,
+ done,
+ label,
+}: {
+ active: boolean
+ done: boolean
+ label: string
+}) {
+ return (
+
+ {label}
+
+ )
+}
diff --git a/components/MatrixOnboardingFlow.tsx b/components/MatrixOnboardingFlow.tsx
new file mode 100644
index 0000000..4c97d7c
--- /dev/null
+++ b/components/MatrixOnboardingFlow.tsx
@@ -0,0 +1,533 @@
+'use client'
+
+import { useEffect, useMemo, useState } from 'react'
+import { SiweMessage } from 'siwe'
+import { createWalletClient, custom } from 'viem'
+import { base } from 'viem/chains'
+import { getResponseRequestId, trackClientEvent } from '@/lib/client-telemetry'
+
+const ALERT_TEXT = 'ALERT! This is a agent only website, no humans allowed'
+const WORD_TARGET = 365
+const TIME_LIMIT_SECONDS = 60
+
+const onboardingSteps = [
+ 'Step 1: Confirm robot identity and initiate agent registration.',
+ 'Step 2: Connect wallet and verify AgentBook registration with signed challenge.',
+ 'Step 3: Complete inverted CAPTCHA before claim unlock.',
+]
+
+const WORD_BANK = [
+ 'agent',
+ 'neural',
+ 'matrix',
+ 'nonce',
+ 'wallet',
+ 'proof',
+ 'signal',
+ 'cipher',
+ 'orbit',
+ 'vector',
+ 'console',
+ 'kernel',
+ 'access',
+ 'verify',
+ 'entropy',
+ 'daemon',
+ 'packet',
+ 'runtime',
+ 'schema',
+ 'protocol',
+ 'binary',
+ 'ledger',
+ 'network',
+ 'module',
+ 'prompt',
+ 'ciphertext',
+ 'latency',
+ 'gateway',
+ 'session',
+ 'trigger',
+ 'compile',
+ 'execute',
+ 'uplink',
+ 'quantum',
+ 'command',
+ 'payload',
+ 'channel',
+ 'replay',
+ 'vectorized',
+ 'response',
+]
+
+type ClaimState = 'idle' | 'fetching-challenge' | 'signing' | 'submitting' | 'success' | 'error'
+type VerifyState = 'idle' | 'loading' | 'success' | 'error'
+
+function createWordSet(total: number) {
+ return Array.from({ length: total }, () => WORD_BANK[Math.floor(Math.random() * WORD_BANK.length)])
+}
+
+export default function MatrixOnboardingFlow() {
+ const [typedText, setTypedText] = useState('')
+ const [isTypingDone, setIsTypingDone] = useState(false)
+ const [showRobotButton, setShowRobotButton] = useState(false)
+ const [robotClicked, setRobotClicked] = useState(false)
+ const [onboardingComplete, setOnboardingComplete] = useState(false)
+ const [showOnboardingModal, setShowOnboardingModal] = useState(false)
+ const [onboardingStep, setOnboardingStep] = useState(0)
+
+ const [walletAddress, setWalletAddress] = useState<`0x${string}` | null>(null)
+ const [verifyState, setVerifyState] = useState('idle')
+ const [claimState, setClaimState] = useState('idle')
+ const [claimError, setClaimError] = useState(null)
+ const [txHash, setTxHash] = useState(null)
+
+ const [words, setWords] = useState([])
+ const [wordIndex, setWordIndex] = useState(0)
+ const [typedWord, setTypedWord] = useState('')
+ const [timeLeft, setTimeLeft] = useState(TIME_LIMIT_SECONDS)
+ const [captchaActive, setCaptchaActive] = useState(false)
+ const [captchaPassed, setCaptchaPassed] = useState(false)
+ const [captchaFailed, setCaptchaFailed] = useState(false)
+
+ const [logs, setLogs] = useState(['System idle. Awaiting robot authentication.'])
+
+ useEffect(() => {
+ let index = 0
+ const timer = setInterval(() => {
+ index += 1
+ setTypedText(ALERT_TEXT.slice(0, index))
+ if (index >= ALERT_TEXT.length) {
+ clearInterval(timer)
+ setIsTypingDone(true)
+ }
+ }, 40)
+ return () => clearInterval(timer)
+ }, [])
+
+ useEffect(() => {
+ if (!isTypingDone) return
+ const timer = setTimeout(() => setShowRobotButton(true), 400)
+ return () => clearTimeout(timer)
+ }, [isTypingDone])
+
+ useEffect(() => {
+ if (!captchaActive || captchaPassed || captchaFailed) return
+ const timer = setInterval(() => {
+ setTimeLeft(prev => {
+ if (prev <= 1) {
+ clearInterval(timer)
+ setCaptchaFailed(true)
+ setCaptchaActive(false)
+ setLogs(existing => [...existing, 'Typing challenge failed: time limit exceeded.'])
+ return 0
+ }
+ return prev - 1
+ })
+ }, 1000)
+ return () => clearInterval(timer)
+ }, [captchaActive, captchaFailed, captchaPassed])
+
+ const appendLog = (line: string) => {
+ setLogs(prev => [...prev, line])
+ }
+
+ const onRobotClick = () => {
+ if (robotClicked) return
+ setRobotClicked(true)
+ setShowOnboardingModal(true)
+ appendLog('Robot handshake accepted.')
+ }
+
+ const finishOnboarding = (skip: boolean) => {
+ setOnboardingComplete(true)
+ setShowOnboardingModal(false)
+ setOnboardingStep(0)
+ appendLog(skip ? 'Onboarding skipped. Starting registration flow.' : 'Onboarding complete.')
+ }
+
+ const nextOnboarding = () => {
+ if (onboardingStep === onboardingSteps.length - 1) {
+ finishOnboarding(false)
+ return
+ }
+ setOnboardingStep(step => step + 1)
+ }
+
+ const connect = async () => {
+ const ethereum = (
+ window as unknown as { ethereum?: { request: (...args: unknown[]) => Promise } }
+ ).ethereum
+ if (!ethereum) {
+ appendLog('No wallet found. Install MetaMask.')
+ return
+ }
+ const client = createWalletClient({ chain: base, transport: custom(ethereum) })
+ const [addr] = await client.requestAddresses()
+ setWalletAddress(addr)
+ appendLog(`Wallet linked: ${addr.slice(0, 6)}...${addr.slice(-4)}`)
+ }
+
+ const verifyAgent = async () => {
+ if (!walletAddress) return
+ setVerifyState('loading')
+ appendLog('Issuing verification challenge...')
+ trackClientEvent({ event: 'agent_verification.start', step: 'challenge-fetch' })
+ try {
+ const challengeRes = await fetch('/api/agent-challenge')
+ if (!challengeRes.ok) throw new Error('Failed to issue verification challenge')
+ const challengeRequestId = getResponseRequestId(challengeRes)
+ const challenge = await challengeRes.json()
+ trackClientEvent({
+ event: 'agent_verification.challenge_issued',
+ requestId: challengeRequestId,
+ route: '/api/agent-challenge',
+ })
+
+ const ethereum = (
+ window as unknown as {
+ ethereum?: { request: (...args: unknown[]) => Promise }
+ }
+ ).ethereum
+ const client = createWalletClient({ chain: base, transport: custom(ethereum!) })
+ const payload = {
+ capability: challenge.payloadTemplate.capability,
+ challengeId: challenge.payloadTemplate.challengeId,
+ nonce: challenge.payloadTemplate.nonce,
+ schemaVersion: challenge.payloadTemplate.schemaVersion,
+ sdk: challenge.payloadTemplate.sdk,
+ timestamp: challenge.payloadTemplate.timestamp,
+ }
+ const message = `agent-capability:${JSON.stringify(payload)}`
+ appendLog('Signing deterministic verification payload...')
+ const signature = await client.signMessage({
+ account: walletAddress,
+ message,
+ })
+
+ const verifyRes = await fetch('/api/agent-challenge', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ challengeId: challenge.challengeId,
+ proof: {
+ address: walletAddress,
+ nonce: challenge.nonce,
+ signature,
+ delegation: {
+ worldIdSignal: walletAddress,
+ },
+ payload: challenge.payloadTemplate,
+ },
+ }),
+ })
+ const verifyRequestId = getResponseRequestId(verifyRes)
+ const verifyData = await verifyRes.json()
+ if (!verifyRes.ok) throw new Error(verifyData.error || 'Agent verification failed')
+
+ setVerifyState('success')
+ appendLog('Agent verified successfully.')
+ trackClientEvent({
+ event: 'agent_verification.success',
+ requestId: verifyRequestId,
+ route: '/api/agent-challenge',
+ })
+ } catch (error) {
+ setVerifyState('error')
+ appendLog(`Verification failed: ${(error as Error).message}`)
+ trackClientEvent({
+ level: 'error',
+ event: 'agent_verification.failed',
+ message: (error as Error).message,
+ })
+ }
+ }
+
+ const startTypingChallenge = () => {
+ setWords(createWordSet(WORD_TARGET))
+ setWordIndex(0)
+ setTypedWord('')
+ setTimeLeft(TIME_LIMIT_SECONDS)
+ setCaptchaActive(true)
+ setCaptchaPassed(false)
+ setCaptchaFailed(false)
+ appendLog(`Typing challenge started (${WORD_TARGET} words in ${TIME_LIMIT_SECONDS}s).`)
+ }
+
+ const submitTypedWord = () => {
+ if (!captchaActive || captchaPassed || captchaFailed) return
+ const value = typedWord.trim().toLowerCase()
+ if (!value) return
+
+ const expected = words[wordIndex]
+ if (value === expected) {
+ const next = wordIndex + 1
+ setWordIndex(next)
+ if (next >= WORD_TARGET) {
+ setCaptchaPassed(true)
+ setCaptchaActive(false)
+ appendLog('Typing challenge passed. Claim unlocked.')
+ }
+ } else {
+ appendLog(`Mismatch: expected "${expected}", received "${value}".`)
+ }
+ setTypedWord('')
+ }
+
+ const claim = async () => {
+ if (!walletAddress || !captchaPassed || verifyState !== 'success') return
+ setClaimError(null)
+ setClaimState('fetching-challenge')
+ appendLog('Fetching CAIP-122 challenge for claim...')
+ trackClientEvent({ event: 'claim.start', step: 'challenge-fetch' })
+ try {
+ const challengeRes = await fetch('/api/challenge')
+ if (!challengeRes.ok) throw new Error('Failed to fetch challenge')
+ const challengeRequestId = getResponseRequestId(challengeRes)
+ const challenge = await challengeRes.json()
+ trackClientEvent({
+ event: 'claim.challenge_received',
+ requestId: challengeRequestId,
+ route: '/api/challenge',
+ })
+
+ setClaimState('signing')
+ appendLog('Signing claim payload...')
+ const ethereum = (
+ window as unknown as {
+ ethereum?: { request: (...args: unknown[]) => Promise }
+ }
+ ).ethereum
+ const client = createWalletClient({ chain: base, transport: custom(ethereum!) })
+ const chainIdNum = parseInt(challenge.chainId.split(':')[1])
+ const message = new SiweMessage({
+ domain: challenge.domain,
+ address: walletAddress,
+ statement: 'Agent authorization for DROP claim',
+ uri: challenge.uri,
+ version: '1',
+ chainId: chainIdNum,
+ nonce: challenge.nonce,
+ issuedAt: challenge.issuedAt,
+ expirationTime: challenge.expirationTime,
+ })
+ const signature = await client.signMessage({
+ account: walletAddress,
+ message: message.prepareMessage(),
+ })
+
+ setClaimState('submitting')
+ appendLog('Submitting verified claim...')
+ const payloadData = {
+ domain: message.domain,
+ address: message.address,
+ statement: message.statement,
+ uri: message.uri,
+ version: message.version,
+ chainId: challenge.chainId,
+ nonce: message.nonce,
+ issuedAt: message.issuedAt,
+ expirationTime: message.expirationTime,
+ signature,
+ type: 'eip191' as const,
+ }
+
+ const res = await fetch('/api/claim', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ payload: btoa(JSON.stringify(payloadData)) }),
+ })
+ const claimRequestId = getResponseRequestId(res)
+ const data = await res.json()
+ if (!res.ok) throw new Error(data.error || 'Claim failed')
+
+ setClaimState('success')
+ setTxHash(data.txHash)
+ appendLog(`Claim success: ${data.txHash.slice(0, 10)}...`)
+ trackClientEvent({
+ event: 'claim.success',
+ requestId: claimRequestId,
+ route: '/api/claim',
+ })
+ } catch (error) {
+ const message = (error as Error).message
+ setClaimState('error')
+ setClaimError(message)
+ appendLog(`Claim failed: ${message}`)
+ trackClientEvent({
+ level: 'error',
+ event: 'claim.failed',
+ message,
+ })
+ }
+ }
+
+ const currentWords = useMemo(() => words.slice(wordIndex, wordIndex + 10), [wordIndex, words])
+ const claimLoading =
+ claimState === 'fetching-challenge' || claimState === 'signing' || claimState === 'submitting'
+
+ return (
+
+
+
+ {typedText}
+ {!isTypingDone && | }
+
+
+ {showRobotButton && (
+
+ {robotClicked ? 'ROBOT CONFIRMED' : 'ARE YOU A ROBOT?'}
+
+ )}
+
+ {showOnboardingModal && (
+
+
+
Onboarding
+
{onboardingSteps[onboardingStep]}
+
+ finishOnboarding(true)}
+ className="text-xs font-medium text-text-muted hover:text-text-secondary"
+ >
+ Skip
+
+
+ {onboardingStep === onboardingSteps.length - 1 ? 'Start' : 'Next'}
+
+
+
+
+ )}
+
+ {onboardingComplete && (
+
+
+ Agent Registration Stage
+
+ {!walletAddress ? (
+
+ Connect Wallet
+
+ ) : (
+
+
+ Connected: {walletAddress.slice(0, 6)}...{walletAddress.slice(-4)}
+
+
+ {verifyState === 'idle' && 'Verify Agent Registration'}
+ {verifyState === 'loading' && 'Verifying...'}
+ {verifyState === 'success' && 'Agent Verified'}
+ {verifyState === 'error' && 'Retry Verification'}
+
+
+ )}
+
+ )}
+
+ {verifyState === 'success' && (
+
+
+ Inverted CAPTCHA Challenge
+
+ {!captchaActive && !captchaPassed && (
+
+ Start 365-Word Challenge
+
+ )}
+
+ {(captchaActive || captchaPassed || captchaFailed) && (
+
+
+ Progress: {wordIndex}/{WORD_TARGET} | Time: {timeLeft}s
+
+
+
{currentWords.join(' ')}
+
+
setTypedWord(e.target.value)}
+ onPaste={e => {
+ e.preventDefault()
+ appendLog('Paste blocked. Manual typing required.')
+ }}
+ onKeyDown={e => {
+ if (e.key === ' ' || e.key === 'Enter') {
+ e.preventDefault()
+ submitTypedWord()
+ }
+ }}
+ disabled={!captchaActive}
+ placeholder="Type next word and press space"
+ className="w-full rounded-md border border-primary/30 bg-black/60 px-3 py-2 font-mono text-xs text-green-100 outline-none focus:border-primary/60 disabled:opacity-60"
+ />
+
+ {captchaFailed && (
+
+ Retry Challenge
+
+ )}
+
+ )}
+
+ )}
+
+ {walletAddress && verifyState === 'success' && (
+
+
+ {!captchaPassed && 'Claim Locked (Pass CAPTCHA)'}
+ {captchaPassed && claimState === 'idle' && 'Claim Token'}
+ {claimState === 'fetching-challenge' && 'Fetching Challenge...'}
+ {claimState === 'signing' && 'Signing...'}
+ {claimState === 'submitting' && 'Submitting...'}
+ {claimState === 'success' && 'Claimed'}
+ {claimState === 'error' && 'Retry Claim'}
+
+ {txHash && (
+
+ View transaction
+
+ )}
+ {claimError &&
{claimError}
}
+
+ )}
+
+
+ {logs.map((log, idx) => (
+
+ > {log}
+
+ ))}
+
+
+
+ )
+}
diff --git a/components/ThemeToggle.tsx b/components/ThemeToggle.tsx
new file mode 100644
index 0000000..d7be26a
--- /dev/null
+++ b/components/ThemeToggle.tsx
@@ -0,0 +1,30 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+
+export default function ThemeToggle() {
+ const [dark, setDark] = useState(true)
+
+ useEffect(() => {
+ const saved = window.localStorage.getItem('theme')
+ const isDark = saved !== 'light'
+ setDark(isDark)
+ document.documentElement.classList.toggle('dark', isDark)
+ }, [])
+
+ const toggle = () => {
+ const next = !dark
+ setDark(next)
+ document.documentElement.classList.toggle('dark', next)
+ window.localStorage.setItem('theme', next ? 'dark' : 'light')
+ }
+
+ return (
+
+ {dark ? 'Light mode' : 'Dark mode'}
+
+ )
+}
diff --git a/contracts/foundry.toml b/contracts/foundry.toml
index e3c5ba2..c1e903a 100644
--- a/contracts/foundry.toml
+++ b/contracts/foundry.toml
@@ -1,17 +1,4 @@
[profile.default]
src = "src"
out = "out"
-libs = ["lib", "dependencies"]
-
-[rpc_endpoints]
-base = "https://mainnet.base.org"
-base_sepolia = "https://sepolia.base.org"
-
-[etherscan]
-base = { key = "${BASESCAN_API_KEY}", url = "https://api.basescan.org/api" }
-base_sepolia = { key = "${BASESCAN_API_KEY}", url = "https://api-sepolia.basescan.org/api" }
-
-[dependencies]
-solady = { version = "0.0.210" }
-forge-std = { version = "1.8.2" }
-"@openzeppelin-contracts" = { version = "5.0.2" }
+libs = ["../../node_modules"]
diff --git a/contracts/remappings.txt b/contracts/remappings.txt
index 3369bbc..c8a601c 100644
--- a/contracts/remappings.txt
+++ b/contracts/remappings.txt
@@ -1,4 +1,5 @@
-solady-0.0.210/=dependencies/solady-0.0.210/
+@openzeppelin-contracts-5.0.2/=dependencies/@openzeppelin-contracts-5.0.2/
forge-std-1.8.2/=dependencies/forge-std-1.8.2/
+solady-0.0.210/=dependencies/solady-0.0.210/
world-id-contracts/=dependencies/world-id-contracts/src/
-@openzeppelin-contracts-5.0.2/=dependencies/@openzeppelin-contracts-5.0.2/
+forge-std/=dependencies/forge-std-1.8.2/src/
diff --git a/contracts/src/DroppingAirToken.sol b/contracts/src/DroppingAirToken.sol
new file mode 100644
index 0000000..cc6e545
--- /dev/null
+++ b/contracts/src/DroppingAirToken.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract DroppingAirToken is ERC20 {
+ constructor(uint256 initialSupply) ERC20("Dropping Air", "DROP") {
+ _mint(msg.sender, initialSupply);
+ }
+}
diff --git a/contracts/src/SybilAirdrop.sol b/contracts/src/SybilAirdrop.sol
new file mode 100644
index 0000000..6077f2e
--- /dev/null
+++ b/contracts/src/SybilAirdrop.sol
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+
+contract SybilAirdrop is Ownable {
+ IERC20 public immutable token;
+ uint256 public immutable claimAmount;
+ uint256 public totalClaims;
+
+ mapping(uint256 => bool) public hasClaimed;
+
+ event Claimed(address indexed agent, uint256 indexed nullifierHash, uint256 amount);
+
+ constructor(address _token, uint256 _claimAmount) Ownable(msg.sender) {
+ token = IERC20(_token);
+ claimAmount = _claimAmount;
+ }
+
+ function claim(address agent, uint256 nullifierHash) external onlyOwner {
+ require(!hasClaimed[nullifierHash], "Already claimed");
+ require(token.balanceOf(address(this)) >= claimAmount, "Insufficient funds");
+
+ hasClaimed[nullifierHash] = true;
+ totalClaims++;
+ require(token.transfer(agent, claimAmount), "Transfer failed");
+
+ emit Claimed(agent, nullifierHash, claimAmount);
+ }
+
+ function remainingBalance() external view returns (uint256) {
+ return token.balanceOf(address(this));
+ }
+
+ function withdraw() external onlyOwner {
+ uint256 balance = token.balanceOf(address(this));
+ require(token.transfer(owner(), balance), "Transfer failed");
+ }
+}
diff --git a/cspell.json b/cspell.json
new file mode 100644
index 0000000..4e5c1f1
--- /dev/null
+++ b/cspell.json
@@ -0,0 +1,45 @@
+{
+ "version": "0.2",
+ "enabled": true,
+ "language": "en-US",
+ "allowCompoundWords": true,
+ "dictionaries": ["typescript", "node", "npm", "html"],
+ "enabledLanguageIds": [
+ "typescript",
+ "typescriptreact",
+ "javascript",
+ "markdown",
+ "yaml",
+ "json"
+ ],
+ "words": [
+ "agentkit",
+ "agentbook",
+ "airdrop",
+ "basescan",
+ "clsx",
+ "formatjs",
+ "idkit",
+ "merkle",
+ "minikit",
+ "nullifier",
+ "pkce",
+ "siwe",
+ "supabase",
+ "sybil",
+ "testid",
+ "USDCE",
+ "viem",
+ "webp"
+ ],
+ "ignorePaths": [
+ "node_modules",
+ ".next",
+ "pnpm-lock.yaml",
+ "contracts",
+ "prisma",
+ "scripts",
+ "design-tokens.json",
+ "sybil-airdrop"
+ ]
+}
diff --git a/design-tokens.json b/design-tokens.json
new file mode 100644
index 0000000..f53ab1d
--- /dev/null
+++ b/design-tokens.json
@@ -0,0 +1,97 @@
+{
+ "$type": "designTokens",
+ "$description": "Dropping Air — Minimalist design system. Import via Tokens Studio for Figma.",
+ "color": {
+ "neutral": {
+ "0": { "$value": "#FFFFFF", "$type": "color" },
+ "50": { "$value": "#F9FAFB", "$type": "color" },
+ "100": { "$value": "#F3F4F6", "$type": "color" },
+ "200": { "$value": "#E5E7EB", "$type": "color" },
+ "300": { "$value": "#D1D5DB", "$type": "color" },
+ "400": { "$value": "#9CA3AF", "$type": "color" },
+ "500": { "$value": "#6B7280", "$type": "color" },
+ "600": { "$value": "#4B5563", "$type": "color" },
+ "700": { "$value": "#374151", "$type": "color" },
+ "800": { "$value": "#1F2937", "$type": "color" },
+ "900": { "$value": "#111827", "$type": "color" },
+ "950": { "$value": "#030712", "$type": "color" }
+ },
+ "primary": {
+ "50": { "$value": "#EFF6FF", "$type": "color" },
+ "100": { "$value": "#DBEAFE", "$type": "color" },
+ "500": { "$value": "#3B82F6", "$type": "color" },
+ "600": { "$value": "#2563EB", "$type": "color" },
+ "700": { "$value": "#1D4ED8", "$type": "color" }
+ },
+ "success": {
+ "50": { "$value": "#F0FDF4", "$type": "color" },
+ "500": { "$value": "#22C55E", "$type": "color" },
+ "700": { "$value": "#15803D", "$type": "color" }
+ },
+ "error": {
+ "50": { "$value": "#FEF2F2", "$type": "color" },
+ "500": { "$value": "#EF4444", "$type": "color" },
+ "700": { "$value": "#B91C1C", "$type": "color" }
+ },
+ "warning": {
+ "50": { "$value": "#FFFBEB", "$type": "color" },
+ "500": { "$value": "#F59E0B", "$type": "color" },
+ "700": { "$value": "#B45309", "$type": "color" }
+ },
+ "semantic": {
+ "bg": { "$value": "{color.neutral.0}", "$type": "color" },
+ "bg-subtle": { "$value": "{color.neutral.50}", "$type": "color" },
+ "surface": { "$value": "{color.neutral.0}", "$type": "color" },
+ "surface-raised": { "$value": "{color.neutral.50}", "$type": "color" },
+ "border": { "$value": "{color.neutral.200}", "$type": "color" },
+ "border-subtle": { "$value": "{color.neutral.100}", "$type": "color" },
+ "text": { "$value": "{color.neutral.900}", "$type": "color" },
+ "text-secondary": { "$value": "{color.neutral.500}", "$type": "color" },
+ "text-muted": { "$value": "{color.neutral.400}", "$type": "color" }
+ }
+ },
+ "fontFamily": {
+ "sans": { "$value": "Inter, system-ui, -apple-system, sans-serif", "$type": "fontFamily" },
+ "mono": { "$value": "JetBrains Mono, ui-monospace, monospace", "$type": "fontFamily" }
+ },
+ "fontSize": {
+ "xs": { "$value": "0.75rem", "$type": "dimension" },
+ "sm": { "$value": "0.875rem", "$type": "dimension" },
+ "base": { "$value": "1rem", "$type": "dimension" },
+ "lg": { "$value": "1.125rem", "$type": "dimension" },
+ "xl": { "$value": "1.25rem", "$type": "dimension" },
+ "2xl": { "$value": "1.5rem", "$type": "dimension" },
+ "3xl": { "$value": "1.875rem", "$type": "dimension" }
+ },
+ "fontWeight": {
+ "normal": { "$value": "400", "$type": "fontWeight" },
+ "medium": { "$value": "500", "$type": "fontWeight" },
+ "semibold": { "$value": "600", "$type": "fontWeight" }
+ },
+ "spacing": {
+ "1": { "$value": "0.25rem", "$type": "dimension" },
+ "2": { "$value": "0.5rem", "$type": "dimension" },
+ "3": { "$value": "0.75rem", "$type": "dimension" },
+ "4": { "$value": "1rem", "$type": "dimension" },
+ "5": { "$value": "1.25rem", "$type": "dimension" },
+ "6": { "$value": "1.5rem", "$type": "dimension" },
+ "8": { "$value": "2rem", "$type": "dimension" },
+ "10": { "$value": "2.5rem", "$type": "dimension" },
+ "12": { "$value": "3rem", "$type": "dimension" },
+ "16": { "$value": "4rem", "$type": "dimension" }
+ },
+ "borderRadius": {
+ "sm": { "$value": "0.375rem", "$type": "dimension" },
+ "md": { "$value": "0.5rem", "$type": "dimension" },
+ "lg": { "$value": "0.75rem", "$type": "dimension" },
+ "xl": { "$value": "1rem", "$type": "dimension" },
+ "full": { "$value": "9999px", "$type": "dimension" }
+ },
+ "shadow": {
+ "sm": { "$value": "0 1px 2px 0 rgba(0,0,0,0.05)", "$type": "boxShadow" },
+ "base": {
+ "$value": "0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1)",
+ "$type": "boxShadow"
+ }
+ }
+}
diff --git a/docs/OBSERVABILITY_RUNBOOK.md b/docs/OBSERVABILITY_RUNBOOK.md
new file mode 100644
index 0000000..444e5eb
--- /dev/null
+++ b/docs/OBSERVABILITY_RUNBOOK.md
@@ -0,0 +1,145 @@
+# Observability Runbook
+
+This runbook defines how to debug and operate both app surfaces:
+
+- Root app (`/app`, `/components`, `/lib`)
+- `sybil-airdrop` app (`/sybil-airdrop/src/app`, `/sybil-airdrop/src/components`, `/sybil-airdrop/src/lib`)
+
+## What Is Instrumented
+
+- Structured JSON logs with shared fields:
+ - `timestamp`, `level`, `event`, `service`, `env`, `traceId`, `spanId`, `requestId`
+ - Route context (`route`, `method`, `durationMs`)
+ - Safe actor context (`wallet` masked as `0x1234...abcd`)
+- Sensitive value redaction:
+ - `authorization`, `signature`, `token`, `privateKey`, `proof`, `payload`
+- Request correlation:
+ - API handlers read/generate `x-request-id`
+ - Responses echo `x-request-id`
+ - JSON responses include `requestId`
+- Error taxonomy:
+ - `BAD_REQUEST`, `NONCE_INVALID`, `NONCE_EXPIRED`, `INVALID_SIGNATURE`
+ - `AGENT_NOT_REGISTERED`, `CLAIM_ALREADY_EXISTS`
+ - `DB_READ_FAILED`, `DB_WRITE_FAILED`, `CHAIN_TX_FAILED`
+ - `CHALLENGE_NOT_FOUND`, `CHALLENGE_USED`, `DETERMINISTIC_PAYLOAD_MISMATCH`
+ - `RP_CONTEXT_FAILED`, `INTERNAL_ERROR`
+- OpenTelemetry tracing primitives:
+ - `withSpan(...)` wrappers around challenge creation, signature verification, AgentBook lookup, DB reads/writes, and claim execution.
+
+## Key Log Events
+
+- Claim flow:
+ - `claim.request.start`
+ - `claim.validation.failed`
+ - `claim.signature.invalid`
+ - `claim.agent_not_registered`
+ - `claim.already_exists`
+ - `claim.insert.failed`
+ - `claim.request.success`
+ - `claim.request.failed`
+- Challenge flow:
+ - `challenge.request.start`
+ - `challenge.insert.failed`
+ - `challenge.request.success`
+ - `challenge.request.failed`
+- Agent challenge flow:
+ - `agent_challenge.issue.start`
+ - `agent_challenge.issue.success`
+ - `agent_challenge.verify.start`
+ - `agent_challenge.verify.success`
+ - `agent_challenge.verify.failed`
+- Sybil status + RP context:
+ - `status.request.start`
+ - `status.request.success`
+ - `status.request.failed`
+ - `rp_context.request.start`
+ - `rp_context.request.success`
+ - `rp_context.request.failed`
+
+## Baseline Dashboards
+
+Create dashboards grouped by `service` and `route`.
+
+1) Error rate by route and code
+- Filter: `level=error OR code exists`
+- Group by: `service`, `route`, `code`
+- Window: 5m and 1h
+
+2) Latency by endpoint
+- Filter: `event in (claim.request.success, challenge.request.success, status.request.success, rp_context.request.success)`
+- Metric: `p50`, `p95`, `p99` of `durationMs`
+- Group by: `route`
+
+3) Claim funnel health
+- Sequence counts:
+ - `challenge.request.success`
+ - `claim.request.start`
+ - `claim.request.success`
+- Drop-off metric:
+ - `(claim.request.start - claim.request.success) / claim.request.start`
+
+4) Abuse and verification anomalies
+- Track count over time for:
+ - `code=NONCE_INVALID`
+ - `code=NONCE_EXPIRED`
+ - `code=INVALID_SIGNATURE`
+ - `code=CHALLENGE_USED`
+ - `event=agent_challenge.verify.failed`
+
+## Alert Thresholds
+
+Use rolling 5-minute windows unless otherwise stated.
+
+- Critical: claim failures
+ - Trigger when `claim.request.failed` > 5 within 5m
+ - Trigger when `claim.request.success` drops to 0 for 10m while `claim.request.start` > 10
+- High: latency regressions
+ - Trigger when p95 `durationMs` for `/api/claim` > 4000ms for 10m
+ - Trigger when p95 `durationMs` for `/api/challenge` > 1000ms for 10m
+- Medium: signature/nonce anomalies
+ - Trigger when `code in (INVALID_SIGNATURE, NONCE_INVALID, NONCE_EXPIRED)` > 25 within 10m
+- Medium: RP context
+ - Trigger when `rp_context.request.failed` > 3 within 10m
+
+## Incident Triage Workflow
+
+1) Start with `requestId`
+- Search logs by `requestId`.
+- Reconstruct route lifecycle (`*.start -> *.success|*.failed`).
+
+2) Correlate with tracing
+- Use `traceId` and `spanId` from the same log event.
+- Inspect slow/failing spans:
+ - `claim.lookup_human`
+ - `claim.lookup_existing`
+ - `claim.execute_airdrop`
+ - `claim.insert_record`
+
+3) Classify by code
+- `INVALID_SIGNATURE`: wallet signature mismatch or malformed payload.
+- `NONCE_*`: stale/reused challenge or clock skew.
+- `AGENT_NOT_REGISTERED`: expected for unregistered agents; investigate surge patterns.
+- `DB_*`: Supabase connectivity/table policy issue.
+- `CHAIN_TX_FAILED`: simulation/write contract failure, RPC instability, or signer issue.
+
+4) Containment actions
+- Increase challenge TTL only if expiry spikes are legitimate.
+- Rate-limit offending wallets/IPs when signature/nonce abuse spikes.
+- Disable claim CTA in UI during sustained `CHAIN_TX_FAILED` incidents.
+
+## Client Diagnostics
+
+- Client telemetry logs flow milestones:
+ - challenge fetch started/completed
+ - claim submission started/completed
+ - verification/register failures
+- Correlate browser-side failures with server-side traces via `x-request-id`.
+- In production, client telemetry only emits `error` level to reduce noise.
+
+## Verification Checklist
+
+- Every API response includes:
+ - `x-request-id` response header
+ - `requestId` field in JSON body
+- Top-level exceptions never expose stack traces in API response bodies.
+- Logs do not expose raw signatures, tokens, or private keys.
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..631404f
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,42 @@
+import { FlatCompat } from '@eslint/eslintrc'
+import js from '@eslint/js'
+import ts from 'typescript-eslint'
+
+const compat = new FlatCompat({
+ baseDirectory: import.meta.dirname,
+})
+
+const eslintConfig = [
+ ...compat.config({
+ extends: ['next/core-web-vitals', 'next/typescript'],
+
+ settings: {
+ next: {
+ rootDir: '.',
+ },
+ },
+ }),
+
+ js.configs.recommended,
+ ...ts.configs.recommended,
+
+ {
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+
+ {
+ ignores: [
+ '.next/**/*',
+ 'node_modules/**/*',
+ 'public/**/*',
+ 'contracts/**/*',
+ 'prisma/**/*',
+ 'scripts/**/*',
+ 'sybil-airdrop/**/*',
+ ],
+ },
+]
+
+export default eslintConfig
diff --git a/instrumentation.ts b/instrumentation.ts
new file mode 100644
index 0000000..623dc73
--- /dev/null
+++ b/instrumentation.ts
@@ -0,0 +1,14 @@
+import { logEvent } from '@/lib/observability'
+
+let isBootstrapped = false
+
+export async function register() {
+ if (isBootstrapped) {
+ return
+ }
+ isBootstrapped = true
+
+ logEvent('info', 'otel.bootstrap', {
+ runtime: process.env.NEXT_RUNTIME ?? 'nodejs',
+ })
+}
diff --git a/lib/agentkit.ts b/lib/agentkit.ts
new file mode 100644
index 0000000..bc5aa4c
--- /dev/null
+++ b/lib/agentkit.ts
@@ -0,0 +1,32 @@
+import {
+ parseAgentkitHeader,
+ validateAgentkitMessage,
+ verifyAgentkitSignature,
+ createAgentBookVerifier,
+ type AgentBookVerifier,
+} from '@worldcoin/agentkit'
+import { env } from './config'
+
+let _agentBook: AgentBookVerifier | null = null
+
+export function getAgentBook(): AgentBookVerifier {
+ if (_agentBook) return _agentBook
+ _agentBook = createAgentBookVerifier({
+ contractAddress: env.AGENTBOOK_CONTRACT_ADDRESS as `0x${string}`,
+ rpcUrl: env.AGENTBOOK_RPC_URL,
+ })
+ return _agentBook
+}
+
+export const agentBook = new Proxy({} as AgentBookVerifier, {
+ get(_, prop: string) {
+ const instance = getAgentBook()
+ const value = instance[prop as keyof AgentBookVerifier]
+ if (typeof value === 'function') {
+ return value.bind(instance)
+ }
+ return value
+ },
+})
+
+export { parseAgentkitHeader, validateAgentkitMessage, verifyAgentkitSignature }
diff --git a/lib/airdrop.ts b/lib/airdrop.ts
new file mode 100644
index 0000000..23b8859
--- /dev/null
+++ b/lib/airdrop.ts
@@ -0,0 +1,81 @@
+import { createWalletClient, http, publicActions, parseAbi } from 'viem'
+import { privateKeyToAccount } from 'viem/accounts'
+import { base } from 'viem/chains'
+import { env } from './config'
+
+const SYBIL_AIRDROP_ABI = parseAbi([
+ 'function claim(address agent, uint256 nullifierHash) external',
+ 'function hasClaimed(uint256 nullifierHash) view returns (bool)',
+ 'function remainingBalance() view returns (uint256)',
+ 'function totalClaims() view returns (uint256)',
+ 'function claimAmount() view returns (uint256)',
+ 'function token() view returns (address)',
+])
+
+function getWalletClient() {
+ const account = privateKeyToAccount(env.AIRDROP_SIGNER_PRIVATE_KEY as `0x${string}`)
+ return createWalletClient({
+ account,
+ chain: base,
+ transport: http(env.AGENTBOOK_RPC_URL),
+ }).extend(publicActions)
+}
+
+export async function executeAirdrop(
+ agentAddress: `0x${string}`,
+ nullifierHash: bigint
+): Promise<`0x${string}`> {
+ const client = getWalletClient()
+ const { request } = await client.simulateContract({
+ address: env.AIRDROP_CONTRACT_ADDRESS as `0x${string}`,
+ abi: SYBIL_AIRDROP_ABI,
+ functionName: 'claim',
+ args: [agentAddress, nullifierHash],
+ })
+ return client.writeContract(request)
+}
+
+export async function hasClaimedOnChain(nullifierHash: bigint): Promise {
+ const client = getWalletClient()
+ return client.readContract({
+ address: env.AIRDROP_CONTRACT_ADDRESS as `0x${string}`,
+ abi: SYBIL_AIRDROP_ABI,
+ functionName: 'hasClaimed',
+ args: [nullifierHash],
+ })
+}
+
+export async function getAirdropInfo() {
+ const client = getWalletClient()
+ const contractAddress = env.AIRDROP_CONTRACT_ADDRESS as `0x${string}`
+
+ const [remainingBalance, totalClaims, claimAmount, tokenAddress] = await Promise.all([
+ client.readContract({
+ address: contractAddress,
+ abi: SYBIL_AIRDROP_ABI,
+ functionName: 'remainingBalance',
+ }),
+ client.readContract({
+ address: contractAddress,
+ abi: SYBIL_AIRDROP_ABI,
+ functionName: 'totalClaims',
+ }),
+ client.readContract({
+ address: contractAddress,
+ abi: SYBIL_AIRDROP_ABI,
+ functionName: 'claimAmount',
+ }),
+ client.readContract({
+ address: contractAddress,
+ abi: SYBIL_AIRDROP_ABI,
+ functionName: 'token',
+ }),
+ ])
+
+ return {
+ remainingBalance: remainingBalance.toString(),
+ totalClaims: Number(totalClaims),
+ claimAmount: claimAmount.toString(),
+ tokenAddress,
+ }
+}
diff --git a/lib/client-telemetry.ts b/lib/client-telemetry.ts
new file mode 100644
index 0000000..06af3d3
--- /dev/null
+++ b/lib/client-telemetry.ts
@@ -0,0 +1,38 @@
+type ClientTelemetryLevel = 'info' | 'warn' | 'error'
+
+type ClientTelemetryPayload = {
+ level?: ClientTelemetryLevel
+ event: string
+ requestId?: string | null
+ route?: string
+ errorCode?: string
+ message?: string
+ step?: string
+}
+
+export function getResponseRequestId(response: Response) {
+ return response.headers.get('x-request-id')
+}
+
+export function trackClientEvent(payload: ClientTelemetryPayload) {
+ const body = {
+ timestamp: new Date().toISOString(),
+ source: 'client',
+ ...payload,
+ }
+
+ if (process.env.NODE_ENV === 'production' && payload.level !== 'error') {
+ return
+ }
+
+ const serialized = JSON.stringify(body)
+ if (payload.level === 'error') {
+ console.error(serialized)
+ return
+ }
+ if (payload.level === 'warn') {
+ console.warn(serialized)
+ return
+ }
+ console.info(serialized)
+}
diff --git a/lib/config.test.ts b/lib/config.test.ts
new file mode 100644
index 0000000..629022c
--- /dev/null
+++ b/lib/config.test.ts
@@ -0,0 +1,7 @@
+import { describe, it, expect } from 'vitest'
+
+describe('config', () => {
+ it('validates env schema exists', () => {
+ expect(typeof process.env).toBe('object')
+ })
+})
diff --git a/lib/config.ts b/lib/config.ts
new file mode 100644
index 0000000..b7392db
--- /dev/null
+++ b/lib/config.ts
@@ -0,0 +1,34 @@
+import { z } from 'zod'
+import { logEvent } from '@/lib/observability'
+
+const envSchema = z.object({
+ AGENTBOOK_CONTRACT_ADDRESS: z.string().startsWith('0x'),
+ AGENTBOOK_RPC_URL: z.string(),
+ AIRDROP_CONTRACT_ADDRESS: z.string().startsWith('0x'),
+ AIRDROP_SIGNER_PRIVATE_KEY: z.string().startsWith('0x'),
+ TOKEN_ADDRESS: z.string().startsWith('0x'),
+ CLAIM_AMOUNT: z.string(),
+ NEXT_PUBLIC_CHAIN_ID: z.string(),
+ NEXT_PUBLIC_DOMAIN: z.string(),
+})
+
+let _env: z.infer | null = null
+
+export function getEnv() {
+ if (_env) return _env
+ const result = envSchema.safeParse(process.env)
+ if (!result.success) {
+ logEvent('error', 'config.env.invalid', {
+ issues: result.error.issues.map(issue => issue.path.join('.')).filter(Boolean),
+ })
+ throw new Error('Invalid environment variables')
+ }
+ _env = result.data
+ return _env
+}
+
+export const env = new Proxy({} as z.infer, {
+ get(_, prop: string) {
+ return getEnv()[prop as keyof z.infer]
+ },
+})
diff --git a/lib/contracts.ts b/lib/contracts.ts
new file mode 100644
index 0000000..c04b54f
--- /dev/null
+++ b/lib/contracts.ts
@@ -0,0 +1,7 @@
+import { parseAbi } from 'viem'
+
+export const AGENT_BOOK_ABI = parseAbi([
+ 'function register(address agent, uint256 root, uint256 nonce, uint256 nullifierHash, uint256[8] proof) external',
+ 'function lookupHuman(address) view returns (uint256)',
+ 'function getNextNonce(address) view returns (uint256)',
+])
diff --git a/lib/db.ts b/lib/db.ts
new file mode 100644
index 0000000..3e373a3
--- /dev/null
+++ b/lib/db.ts
@@ -0,0 +1,48 @@
+import { createClient, type SupabaseClient } from '@supabase/supabase-js'
+
+let _db: SupabaseClient | null = null
+
+function getDb(): SupabaseClient {
+ if (_db) return _db
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL
+ const supabaseKey =
+ process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
+ if (!supabaseUrl || !supabaseKey) {
+ throw new Error('Missing Supabase URL or key environment variables')
+ }
+ _db = createClient(supabaseUrl, supabaseKey)
+ return _db
+}
+
+const db = new Proxy({} as SupabaseClient, {
+ get(_, prop: string) {
+ const instance = getDb()
+ const value = instance[prop as keyof SupabaseClient]
+ if (typeof value === 'function') {
+ return value.bind(instance)
+ }
+ return value
+ },
+})
+
+export default db
+
+export type Claim = {
+ id: string
+ nullifierHash: string
+ agentAddress: string
+ txHash: string
+ amount: string
+ claimedAt: string
+}
+
+export type Challenge = {
+ id: string
+ nonce: string
+ domain: string
+ uri: string
+ chainId: string
+ createdAt: string
+ expiresAt: string
+ used: boolean
+}
diff --git a/lib/observability.ts b/lib/observability.ts
new file mode 100644
index 0000000..ed66a29
--- /dev/null
+++ b/lib/observability.ts
@@ -0,0 +1,171 @@
+import { context, trace, SpanStatusCode } from '@opentelemetry/api'
+import { NextRequest, NextResponse } from 'next/server'
+
+type LogLevel = 'info' | 'warn' | 'error'
+
+type LogFields = Record
+
+type RequestContext = {
+ requestId: string
+ route: string
+ method: string
+ startedAt: number
+}
+
+const REDACT_KEYS = [
+ 'authorization',
+ 'signature',
+ 'token',
+ 'privatekey',
+ 'private_key',
+ 'proof',
+ 'payload',
+ 'airdrop_signer_private_key',
+]
+
+export const ERROR_CODES = {
+ BAD_REQUEST: 'BAD_REQUEST',
+ NONCE_INVALID: 'NONCE_INVALID',
+ NONCE_EXPIRED: 'NONCE_EXPIRED',
+ INVALID_SIGNATURE: 'INVALID_SIGNATURE',
+ AGENT_NOT_REGISTERED: 'AGENT_NOT_REGISTERED',
+ CLAIM_ALREADY_EXISTS: 'CLAIM_ALREADY_EXISTS',
+ DB_WRITE_FAILED: 'DB_WRITE_FAILED',
+ DB_READ_FAILED: 'DB_READ_FAILED',
+ CHAIN_TX_FAILED: 'CHAIN_TX_FAILED',
+ CHALLENGE_NOT_FOUND: 'CHALLENGE_NOT_FOUND',
+ CHALLENGE_USED: 'CHALLENGE_USED',
+ DETERMINISTIC_PAYLOAD_MISMATCH: 'DETERMINISTIC_PAYLOAD_MISMATCH',
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
+ RP_CONTEXT_FAILED: 'RP_CONTEXT_FAILED',
+} as const
+
+function createRequestId() {
+ return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`
+}
+
+function redactValue(value: unknown): unknown {
+ if (Array.isArray(value)) {
+ return value.map(redactValue)
+ }
+ if (value && typeof value === 'object') {
+ const redacted: Record = {}
+ for (const [key, entry] of Object.entries(value)) {
+ const normalized = key.toLowerCase()
+ if (REDACT_KEYS.some(secretKey => normalized.includes(secretKey))) {
+ redacted[key] = '[REDACTED]'
+ } else {
+ redacted[key] = redactValue(entry)
+ }
+ }
+ return redacted
+ }
+ return value
+}
+
+export function createRequestContext(req: NextRequest, route: string): RequestContext {
+ return {
+ requestId: req.headers.get('x-request-id') ?? createRequestId(),
+ route,
+ method: req.method,
+ startedAt: Date.now(),
+ }
+}
+
+export function createSyntheticRequestContext(route: string, method: string): RequestContext {
+ return {
+ requestId: createRequestId(),
+ route,
+ method,
+ startedAt: Date.now(),
+ }
+}
+
+export function logEvent(level: LogLevel, event: string, fields: LogFields = {}) {
+ const activeSpan = trace.getSpan(context.active())
+ const payload = redactValue({
+ timestamp: new Date().toISOString(),
+ level,
+ event,
+ env: process.env.NODE_ENV ?? 'development',
+ service: 'dropping-air',
+ traceId: activeSpan?.spanContext().traceId,
+ spanId: activeSpan?.spanContext().spanId,
+ ...fields,
+ })
+
+ const serialized = JSON.stringify(payload)
+ if (level === 'error') {
+ console.error(serialized)
+ return
+ }
+ if (level === 'warn') {
+ console.warn(serialized)
+ return
+ }
+ console.info(serialized)
+}
+
+export async function withSpan(
+ spanName: string,
+ attributes: Record,
+ run: () => Promise
+) {
+ const tracer = trace.getTracer('dropping-air')
+ return tracer.startActiveSpan(spanName, { attributes }, async span => {
+ try {
+ const result = await run()
+ span.setStatus({ code: SpanStatusCode.OK })
+ return result
+ } catch (error) {
+ span.recordException(error as Error)
+ span.setStatus({ code: SpanStatusCode.ERROR })
+ throw error
+ } finally {
+ span.end()
+ }
+ })
+}
+
+export function responseWithRequestId(
+ ctx: RequestContext,
+ body: Record,
+ status = 200
+) {
+ return NextResponse.json(
+ {
+ ...body,
+ requestId: ctx.requestId,
+ },
+ {
+ status,
+ headers: { 'x-request-id': ctx.requestId },
+ }
+ )
+}
+
+export function maskAddress(address: string | null | undefined) {
+ if (!address) return null
+ if (address.length < 10) return address
+ return `${address.slice(0, 6)}...${address.slice(-4)}`
+}
+
+export function responseError(
+ ctx: RequestContext,
+ status: number,
+ code: (typeof ERROR_CODES)[keyof typeof ERROR_CODES],
+ message: string
+) {
+ return responseWithRequestId(
+ ctx,
+ {
+ error: message,
+ code,
+ },
+ status
+ )
+}
+
+export function elapsedMs(startedAt: number) {
+ return Date.now() - startedAt
+}
diff --git a/next.config.ts b/next.config.ts
new file mode 100644
index 0000000..e4f5738
--- /dev/null
+++ b/next.config.ts
@@ -0,0 +1,5 @@
+import type { NextConfig } from 'next'
+
+const nextConfig: NextConfig = {}
+
+export default nextConfig
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..df56977
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3648 @@
+{
+ "name": "x402-agentkit-poc",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "x402-agentkit-poc",
+ "workspaces": [
+ "agentkit",
+ "sybil-airdrop"
+ ],
+ "devDependencies": {
+ "@typescript-eslint/parser": "^8.56.1",
+ "prettier": "^3.8.1",
+ "prettier-plugin-sort-imports-desc": "^1.0.0",
+ "typescript": "^5.8.3"
+ }
+ },
+ "agentkit": {
+ "name": "@worldcoin/agentkit",
+ "version": "0.0.1",
+ "dependencies": {
+ "@scure/base": "^1.2.6",
+ "@x402/core": "^2.4.0",
+ "siwe": "^2.3.2",
+ "tweetnacl": "^1.0.3",
+ "viem": "^2.46.2",
+ "zod": "^3.24.2"
+ }
+ },
+ "node_modules/@adraffy/ens-normalize": {
+ "version": "1.10.1",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.23.2",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@eslint/object-schema": "^3.0.2",
+ "debug": "^4.3.1",
+ "minimatch": "^10.2.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.5.2",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@eslint/core": "^1.1.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.6.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@eslint/core": "^1.1.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.33.5",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.0.4",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "15.2.0",
+ "license": "MIT"
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "15.2.0",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@noble/ciphers": {
+ "version": "1.3.0",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/curves": {
+ "version": "1.2.0",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@noble/hashes": "1.3.2"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.3.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/secp256k1": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.3.0.tgz",
+ "integrity": "sha512-0TQed2gcBbIrh7Ccyw+y/uZQvbJwm7Ao4scBUxqpBCcsOlZG0O4KGfjtNAy/li4W8n1xt3dxrwJ0beZ2h2G6Kw==",
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@openzeppelin/contracts": {
+ "version": "5.6.1",
+ "license": "MIT"
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
+ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
+ "devOptional": true,
+ "dependencies": {
+ "playwright": "1.58.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@prisma/client": {
+ "version": "6.19.2",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "peerDependencies": {
+ "prisma": "*",
+ "typescript": ">=5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/config": {
+ "version": "6.19.2",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "c12": "3.1.0",
+ "deepmerge-ts": "7.1.5",
+ "effect": "3.18.4",
+ "empathic": "2.0.0"
+ }
+ },
+ "node_modules/@prisma/debug": {
+ "version": "6.19.2",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/engines": {
+ "version": "6.19.2",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "6.19.2",
+ "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
+ "@prisma/fetch-engine": "6.19.2",
+ "@prisma/get-platform": "6.19.2"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "6.19.2",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "6.19.2",
+ "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
+ "@prisma/get-platform": "6.19.2"
+ }
+ },
+ "node_modules/@prisma/get-platform": {
+ "version": "6.19.2",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "6.19.2"
+ }
+ },
+ "node_modules/@scure/base": {
+ "version": "1.2.6",
+ "license": "MIT",
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip32": {
+ "version": "1.7.0",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/curves": "~1.9.0",
+ "@noble/hashes": "~1.8.0",
+ "@scure/base": "~1.2.5"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip32/node_modules/@noble/curves": {
+ "version": "1.9.7",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "1.8.0"
+ },
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip32/node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip39": {
+ "version": "1.6.0",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "~1.8.0",
+ "@scure/base": "~1.2.5"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip39/node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@spruceid/siwe-parser": {
+ "version": "2.1.2",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@noble/hashes": "^1.1.2",
+ "apg-js": "^4.3.0",
+ "uri-js": "^4.4.1",
+ "valid-url": "^1.0.9"
+ }
+ },
+ "node_modules/@stablelib/binary": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "@stablelib/int": "^1.0.1"
+ }
+ },
+ "node_modules/@stablelib/int": {
+ "version": "1.0.1",
+ "license": "MIT"
+ },
+ "node_modules/@stablelib/random": {
+ "version": "1.0.2",
+ "license": "MIT",
+ "dependencies": {
+ "@stablelib/binary": "^1.0.1",
+ "@stablelib/wipe": "^1.0.1"
+ }
+ },
+ "node_modules/@stablelib/wipe": {
+ "version": "1.0.1",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@supabase/auth-js": {
+ "version": "2.99.0",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.99.0.tgz",
+ "integrity": "sha512-tHiIST/OEoLmWBE+3X69xRY5srJM/lL86KltmMlIfDo9ePJLo14vQQV9T4NF+P+MoGhCwQL1GTmk51zuAFMXKw==",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/auth-js/node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "node_modules/@supabase/functions-js": {
+ "version": "2.99.0",
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.99.0.tgz",
+ "integrity": "sha512-zA9oad6EqGwMLLu2LfP1bXbqKcJGiotAdbdTfZG7YS7619YZQAEgejj9mp+E5vglKE1yMWbKK+S1J3PbuUtgLg==",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/functions-js/node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "node_modules/@supabase/postgrest-js": {
+ "version": "2.99.0",
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.99.0.tgz",
+ "integrity": "sha512-8qfOMi2pu9y0IQhUAeFqjrvR49G4ELGevXCWV9qAHXFQ/h2FFh0I8PYjFQj4rHcHSq6hrpozDnS1vbQU8NAQ/A==",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/postgrest-js/node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "node_modules/@supabase/realtime-js": {
+ "version": "2.99.0",
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.99.0.tgz",
+ "integrity": "sha512-7nFTZhNeANR7FvEY6PfWLCfE8dHqcaJd9SuR7IPEZvBPG9K4uEHMivpjZx4NWRSU7Eji7ZbKy2LG+cJ48DhwHg==",
+ "dependencies": {
+ "@types/phoenix": "^1.6.6",
+ "@types/ws": "^8.18.1",
+ "tslib": "2.8.1",
+ "ws": "^8.18.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/realtime-js/node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "node_modules/@supabase/realtime-js/node_modules/ws": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@supabase/storage-js": {
+ "version": "2.99.0",
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.99.0.tgz",
+ "integrity": "sha512-mAEEbfsght5EEALejYrwAP9k8sFBGjfMZT8n4SyMXk2iYuWVeRMs1kA/uKg0uDMctWdZ0bL+L4jZzksUJpCjMA==",
+ "dependencies": {
+ "iceberg-js": "^0.8.1",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/storage-js/node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "node_modules/@supabase/supabase-js": {
+ "version": "2.99.0",
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.99.0.tgz",
+ "integrity": "sha512-SP9Sn9tsHDB7N4u2gT13rdeZJewE4xibAxasG7vOz+fYi92+XkMMbWNx0uGK53zKTnAnvTs16isRooyBy4sn5w==",
+ "dependencies": {
+ "@supabase/auth-js": "2.99.0",
+ "@supabase/functions-js": "2.99.0",
+ "@supabase/postgrest-js": "2.99.0",
+ "@supabase/realtime-js": "2.99.0",
+ "@supabase/storage-js": "2.99.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@swc/helpers/node_modules/tslib": {
+ "version": "2.8.1",
+ "license": "0BSD"
+ },
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.35",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/phoenix": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
+ "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q=="
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.56.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.56.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.56.1",
+ "@typescript-eslint/types": "^8.56.1",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.56.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.56.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.56.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.56.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.56.1",
+ "@typescript-eslint/tsconfig-utils": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.56.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.56.1",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@worldcoin/agentkit": {
+ "resolved": "agentkit",
+ "link": true
+ },
+ "node_modules/@worldcoin/idkit": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/@worldcoin/idkit/-/idkit-4.0.8.tgz",
+ "integrity": "sha512-F3QH1P7oxHPT3syXuVuWsfvt7tAD+mJVBtV6yxT684Zv+NBMLCCwLh7f9Lk7tItlKXKXwFIwJRGJmBgECJcpiQ==",
+ "dependencies": {
+ "@worldcoin/idkit-core": "4.0.11",
+ "qrcode": "^1.5.4"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/@worldcoin/idkit-core": {
+ "version": "4.0.11",
+ "resolved": "https://registry.npmjs.org/@worldcoin/idkit-core/-/idkit-core-4.0.11.tgz",
+ "integrity": "sha512-Fu4oHavbMCS0N8mQrYDpbZ9GZEYJjcAgotcbNw/l+NcuFIDKbo8xleTVz/icyQAaaT0QMkxtagrrH7kTKppG5w==",
+ "dependencies": {
+ "@noble/hashes": "^1.7.2",
+ "@worldcoin/idkit-server": "1.0.0"
+ }
+ },
+ "node_modules/@worldcoin/idkit-core/node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@worldcoin/idkit-server": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@worldcoin/idkit-server/-/idkit-server-1.0.0.tgz",
+ "integrity": "sha512-ZCyXJFexxLjPtG7lxTntf6oiwF0pzoUIsVFIAAgFyXav5KmUnVlrvVoeGrgpiDFRznH2B4/Iv5xv+SkEHxOdXQ==",
+ "dependencies": {
+ "@noble/hashes": "^1.7.2",
+ "@noble/secp256k1": "^2.2.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@worldcoin/idkit-server/node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@x402/core": {
+ "version": "2.5.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "zod": "^3.24.2"
+ }
+ },
+ "node_modules/abitype": {
+ "version": "1.2.3",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/wevm"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.4",
+ "zod": "^3.22.0 || ^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/aes-js": {
+ "version": "4.0.0-beta.5",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/ajv": {
+ "version": "6.14.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/apg-js": {
+ "version": "4.4.0",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.27",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.1",
+ "caniuse-lite": "^1.0.30001774",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
+ "node_modules/c12": {
+ "version": "3.1.0",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^4.0.3",
+ "confbox": "^0.2.2",
+ "defu": "^6.1.4",
+ "dotenv": "^16.6.1",
+ "exsolve": "^1.0.7",
+ "giget": "^2.0.0",
+ "jiti": "^2.4.2",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "perfect-debounce": "^1.0.0",
+ "pkg-types": "^2.2.0",
+ "rc9": "^2.1.2"
+ },
+ "peerDependencies": {
+ "magicast": "^0.3.5"
+ },
+ "peerDependenciesMeta": {
+ "magicast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001776",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/citty": {
+ "version": "0.1.6",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "consola": "^3.2.3"
+ }
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "license": "MIT"
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "license": "MIT"
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/consola": {
+ "version": "3.4.2",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/deepmerge-ts": {
+ "version": "7.1.5",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/defu": {
+ "version": "6.1.4",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/destr": {
+ "version": "2.0.5",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "devOptional": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/effect": {
+ "version": "3.18.4",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "fast-check": "^3.23.1"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.307",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/empathic": {
+ "version": "2.0.0",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "10.0.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.2",
+ "@eslint/config-helpers": "^0.5.2",
+ "@eslint/core": "^1.1.0",
+ "@eslint/plugin-kit": "^0.6.0",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^9.1.1",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.1.1",
+ "esquery": "^1.7.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "minimatch": "^10.2.1",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "9.1.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "11.1.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^5.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ethers": {
+ "version": "6.16.0",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/ethers-io/"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@adraffy/ens-normalize": "1.10.1",
+ "@noble/curves": "1.2.0",
+ "@noble/hashes": "1.3.2",
+ "@types/node": "22.7.5",
+ "aes-js": "4.0.0-beta.5",
+ "tslib": "2.7.0",
+ "ws": "8.17.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/ethers/node_modules/@types/node": {
+ "version": "22.7.5",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "undici-types": "~6.19.2"
+ }
+ },
+ "node_modules/ethers/node_modules/undici-types": {
+ "version": "6.19.8",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/eventemitter3": {
+ "version": "5.0.1",
+ "license": "MIT"
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-check": {
+ "version": "3.23.2",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "pure-rand": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.4",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/giget": {
+ "version": "2.0.0",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "citty": "^0.1.6",
+ "consola": "^3.4.0",
+ "defu": "^6.1.4",
+ "node-fetch-native": "^1.6.6",
+ "nypm": "^0.6.0",
+ "pathe": "^2.0.3"
+ },
+ "bin": {
+ "giget": "dist/cli.mjs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/iceberg-js": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
+ "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.3.4",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/isows": {
+ "version": "1.0.7",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/wevm"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "ws": "*"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.4",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/next": {
+ "version": "15.2.0",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "15.2.0",
+ "@swc/counter": "0.1.3",
+ "@swc/helpers": "0.5.15",
+ "busboy": "1.6.0",
+ "caniuse-lite": "^1.0.30001579",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.6"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "15.2.0",
+ "@next/swc-darwin-x64": "15.2.0",
+ "@next/swc-linux-arm64-gnu": "15.2.0",
+ "@next/swc-linux-arm64-musl": "15.2.0",
+ "@next/swc-linux-x64-gnu": "15.2.0",
+ "@next/swc-linux-x64-musl": "15.2.0",
+ "@next/swc-win32-arm64-msvc": "15.2.0",
+ "@next/swc-win32-x64-msvc": "15.2.0",
+ "sharp": "^0.33.5"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.41.2",
+ "babel-plugin-react-compiler": "*",
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/node-fetch-native": {
+ "version": "1.6.7",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nypm": {
+ "version": "0.6.5",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "citty": "^0.2.0",
+ "pathe": "^2.0.3",
+ "tinyexec": "^1.0.2"
+ },
+ "bin": {
+ "nypm": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nypm/node_modules/citty": {
+ "version": "0.2.1",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/ohash": {
+ "version": "2.0.11",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/ox": {
+ "version": "0.12.4",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/wevm"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@adraffy/ens-normalize": "^1.11.0",
+ "@noble/ciphers": "^1.3.0",
+ "@noble/curves": "1.9.1",
+ "@noble/hashes": "^1.8.0",
+ "@scure/bip32": "^1.7.0",
+ "@scure/bip39": "^1.6.0",
+ "abitype": "^1.2.3",
+ "eventemitter3": "5.0.1"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ox/node_modules/@adraffy/ens-normalize": {
+ "version": "1.11.1",
+ "license": "MIT"
+ },
+ "node_modules/ox/node_modules/@noble/curves": {
+ "version": "1.9.1",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "1.8.0"
+ },
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/ox/node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "2.3.0",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.2",
+ "exsolve": "^1.0.7",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
+ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
+ "devOptional": true,
+ "dependencies": {
+ "playwright-core": "1.58.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
+ "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
+ "devOptional": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.8.1",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-sort-imports-desc": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prettier": "^2.5.1",
+ "typescript": "^4.0.3"
+ }
+ },
+ "node_modules/prettier-plugin-sort-imports-desc/node_modules/prettier": {
+ "version": "2.8.8",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-sort-imports-desc/node_modules/typescript": {
+ "version": "4.9.5",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/prisma": {
+ "version": "6.19.2",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/config": "6.19.2",
+ "@prisma/engines": "6.19.2"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": ">=18.18"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/qrcode": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+ "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/rc9": {
+ "version": "2.1.2",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.4",
+ "destr": "^2.0.3"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "devOptional": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
+ },
+ "node_modules/sharp": {
+ "version": "0.33.5",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.33.5",
+ "@img/sharp-darwin-x64": "0.33.5",
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
+ "@img/sharp-libvips-linux-arm": "1.0.5",
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
+ "@img/sharp-libvips-linux-x64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+ "@img/sharp-linux-arm": "0.33.5",
+ "@img/sharp-linux-arm64": "0.33.5",
+ "@img/sharp-linux-s390x": "0.33.5",
+ "@img/sharp-linux-x64": "0.33.5",
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
+ "@img/sharp-linuxmusl-x64": "0.33.5",
+ "@img/sharp-wasm32": "0.33.5",
+ "@img/sharp-win32-ia32": "0.33.5",
+ "@img/sharp-win32-x64": "0.33.5"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.4",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/siwe": {
+ "version": "2.3.2",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@spruceid/siwe-parser": "^2.1.2",
+ "@stablelib/random": "^1.0.1",
+ "uri-js": "^4.4.1",
+ "valid-url": "^1.0.9"
+ },
+ "peerDependencies": {
+ "ethers": "^5.6.8 || ^6.0.8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.6",
+ "license": "MIT",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/sybil-airdrop": {
+ "resolved": "sybil-airdrop",
+ "link": true
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.19",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/chokidar": {
+ "version": "3.6.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/jiti": {
+ "version": "1.21.7",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/picomatch": {
+ "version": "2.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/readdirp": {
+ "version": "3.6.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tinyexec": {
+ "version": "1.0.2",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.4.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/tslib": {
+ "version": "2.7.0",
+ "license": "0BSD",
+ "peer": true
+ },
+ "node_modules/tweetnacl": {
+ "version": "1.0.3",
+ "license": "Unlicense"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/valid-url": {
+ "version": "1.0.9"
+ },
+ "node_modules/viem": {
+ "version": "2.46.3",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/wevm"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@noble/curves": "1.9.1",
+ "@noble/hashes": "1.8.0",
+ "@scure/bip32": "1.7.0",
+ "@scure/bip39": "1.6.0",
+ "abitype": "1.2.3",
+ "isows": "1.0.7",
+ "ox": "0.12.4",
+ "ws": "8.18.3"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.4"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/viem/node_modules/@noble/curves": {
+ "version": "1.9.1",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "1.8.0"
+ },
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/viem/node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/viem/node_modules/ws": {
+ "version": "8.18.3",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
+ },
+ "node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yargs/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "sybil-airdrop": {
+ "version": "0.1.0",
+ "dependencies": {
+ "@openzeppelin/contracts": "^5.6.1",
+ "@prisma/client": "^6.4.1",
+ "@supabase/supabase-js": "^2.99.0",
+ "@worldcoin/agentkit": "file:../agentkit",
+ "@worldcoin/idkit": "^4.0.8",
+ "next": "15.2.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "siwe": "^2.3.2",
+ "viem": "^2.23.5",
+ "zod": "^3.24.2"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.58.2",
+ "@types/node": "^20.17.22",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.5.3",
+ "prisma": "^6.4.1",
+ "tailwindcss": "^3.4.0",
+ "typescript": "^5.8.3"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 8a31624..0d95dd5 100644
--- a/package.json
+++ b/package.json
@@ -1,31 +1,61 @@
{
- "name": "x402-agentkit-poc",
- "devDependencies": {
- "@typescript-eslint/parser": "^8.56.1",
- "prettier": "^3.8.1",
- "prettier-plugin-sort-imports-desc": "^1.0.0",
- "typescript": "^5.8.3"
- },
- "scripts": {
- "build:agentkit": "bun run --cwd agentkit build",
- "publish:agentkit": "npm publish --workspace @worldcoin/agentkit"
- },
- "prettier": {
- "parser": "@typescript-eslint/parser",
- "semi": false,
- "tabWidth": 4,
- "useTabs": true,
- "printWidth": 120,
- "singleQuote": true,
- "trailingComma": "es5",
- "arrowParens": "avoid",
- "bracketSpacing": true,
- "plugins": [
- "prettier-plugin-sort-imports-desc"
- ]
- },
- "private": true,
- "workspaces": [
- "agentkit"
- ]
+ "name": "dropping-air",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --turbopack",
+ "build": "next build",
+ "start": "next start",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "format": "prettier -w ./",
+ "format:check": "prettier --check ./",
+ "typecheck": "tsc",
+ "spellcheck": "cspell **/*.{js,ts,tsx,md,mdx,json}",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:debug": "vitest --inspect-brk",
+ "db:generate": "prisma generate",
+ "db:push": "prisma db push"
+ },
+ "dependencies": {
+ "@opentelemetry/api": "^1.9.0",
+ "@prisma/client": "^6.4.1",
+ "@supabase/supabase-js": "^2.49.1",
+ "@vercel/analytics": "^1.6.1",
+ "@worldcoin/agentkit": "^0.0.3",
+ "@worldcoin/idkit": "^4.0.8",
+ "next": "15.4.10",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
+ "siwe": "^2.3.2",
+ "viem": "^2.23.5",
+ "zod": "^3.24.0"
+ },
+ "devDependencies": {
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "^9.31.0",
+ "@next/eslint-plugin-next": "^15.4.1",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@types/node": "^24.0.14",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "autoprefixer": "^10.4.21",
+ "cspell": "^9.1.5",
+ "eslint": "^9.31.0",
+ "eslint-config-next": "15.4.1",
+ "eslint-plugin-boundaries": "^5.0.1",
+ "eslint-plugin-react-hooks": "5.2.0",
+ "eslint-plugin-tailwindcss": "^3.18.2",
+ "jsdom": "^26.1.0",
+ "postcss": "^8.5.6",
+ "prettier": "^3.6.2",
+ "prisma": "^6.4.1",
+ "tailwindcss": "^3.4.0",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.8.3",
+ "typescript-eslint": "^8.37.0",
+ "vite": "^6.0.0",
+ "vitest": "^3.2.4"
+ }
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..4b4905e
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,6977 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@opentelemetry/api':
+ specifier: ^1.9.0
+ version: 1.9.0
+ '@prisma/client':
+ specifier: ^6.4.1
+ version: 6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)
+ '@supabase/supabase-js':
+ specifier: ^2.49.1
+ version: 2.99.0
+ '@vercel/analytics':
+ specifier: ^1.6.1
+ version: 1.6.1(next@15.4.10(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
+ '@worldcoin/agentkit':
+ specifier: ^0.0.3
+ version: 0.0.3(ethers@6.16.0)(typescript@5.9.3)
+ '@worldcoin/idkit':
+ specifier: ^4.0.8
+ version: 4.0.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ next:
+ specifier: 15.4.10
+ version: 15.4.10(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react:
+ specifier: ^19.2.3
+ version: 19.2.4
+ react-dom:
+ specifier: ^19.2.3
+ version: 19.2.4(react@19.2.4)
+ siwe:
+ specifier: ^2.3.2
+ version: 2.3.2(ethers@6.16.0)
+ viem:
+ specifier: ^2.23.5
+ version: 2.47.1(typescript@5.9.3)(zod@3.25.76)
+ zod:
+ specifier: ^3.24.0
+ version: 3.25.76
+ devDependencies:
+ '@eslint/eslintrc':
+ specifier: ^3.3.1
+ version: 3.3.5
+ '@eslint/js':
+ specifier: ^9.31.0
+ version: 9.39.4
+ '@next/eslint-plugin-next':
+ specifier: ^15.4.1
+ version: 15.5.12
+ '@testing-library/jest-dom':
+ specifier: ^6.6.3
+ version: 6.9.1
+ '@types/node':
+ specifier: ^24.0.14
+ version: 24.12.0
+ '@types/react':
+ specifier: ^19.2.7
+ version: 19.2.14
+ '@types/react-dom':
+ specifier: ^19.2.3
+ version: 19.2.3(@types/react@19.2.14)
+ autoprefixer:
+ specifier: ^10.4.21
+ version: 10.4.27(postcss@8.5.8)
+ cspell:
+ specifier: ^9.1.5
+ version: 9.7.0
+ eslint:
+ specifier: ^9.31.0
+ version: 9.39.4(jiti@1.21.7)
+ eslint-config-next:
+ specifier: 15.4.1
+ version: 15.4.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ eslint-plugin-boundaries:
+ specifier: ^5.0.1
+ version: 5.4.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))
+ eslint-plugin-react-hooks:
+ specifier: 5.2.0
+ version: 5.2.0(eslint@9.39.4(jiti@1.21.7))
+ eslint-plugin-tailwindcss:
+ specifier: ^3.18.2
+ version: 3.18.2(tailwindcss@3.4.19(yaml@2.8.2))
+ jsdom:
+ specifier: ^26.1.0
+ version: 26.1.0
+ postcss:
+ specifier: ^8.5.6
+ version: 8.5.8
+ prettier:
+ specifier: ^3.6.2
+ version: 3.8.1
+ prisma:
+ specifier: ^6.4.1
+ version: 6.19.2(typescript@5.9.3)
+ tailwindcss:
+ specifier: ^3.4.0
+ version: 3.4.19(yaml@2.8.2)
+ ts-node:
+ specifier: ^10.9.2
+ version: 10.9.2(@types/node@24.12.0)(typescript@5.9.3)
+ typescript:
+ specifier: ^5.8.3
+ version: 5.9.3
+ typescript-eslint:
+ specifier: ^8.37.0
+ version: 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ vite:
+ specifier: ^6.0.0
+ version: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.2)
+ vitest:
+ specifier: ^3.2.4
+ version: 3.2.4(@types/node@24.12.0)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.2)
+
+packages:
+
+ '@adobe/css-tools@4.4.4':
+ resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
+
+ '@adraffy/ens-normalize@1.10.1':
+ resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==}
+
+ '@adraffy/ens-normalize@1.11.1':
+ resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==}
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@asamuzakjp/css-color@3.2.0':
+ resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
+ '@boundaries/elements@1.2.0':
+ resolution: {integrity: sha512-W65Gum02liMd3hmNrLmDBX1u5BmRMcunouFjLXyhxHnNY4YlK1kTxsgfflZ5XBGSnPnO0MkiUzAcoGzYrlx0RQ==}
+ engines: {node: '>=18.18'}
+
+ '@cspell/cspell-bundled-dicts@9.7.0':
+ resolution: {integrity: sha512-s7h1vo++Q3AsfQa3cs0u/KGwm3SYInuIlC4kjlCBWjQmb4KddiZB5O1u0+3TlA7GycHb5M4CR7MDfHUICgJf+w==}
+ engines: {node: '>=20'}
+
+ '@cspell/cspell-json-reporter@9.7.0':
+ resolution: {integrity: sha512-6xpGXlMtQA3hV2BCAQcPkpx9eI12I0o01i9eRqSSEDKtxuAnnrejbcCpL+5OboAjTp3/BSeNYSnhuWYLkSITWQ==}
+ engines: {node: '>=20'}
+
+ '@cspell/cspell-performance-monitor@9.7.0':
+ resolution: {integrity: sha512-w1PZIFXuvjnC6mQHyYAFnrsn5MzKnEcEkcK1bj4OG00bAt7WX2VUA/eNNt9c1iHozCQ+FcRYlfbGxuBmNyzSgw==}
+ engines: {node: '>=20.18'}
+
+ '@cspell/cspell-pipe@9.7.0':
+ resolution: {integrity: sha512-iiisyRpJciU9SOHNSi0ZEK0pqbEMFRatI/R4O+trVKb+W44p4MNGClLVRWPGUmsFbZKPJL3jDtz0wPlG0/JCZA==}
+ engines: {node: '>=20'}
+
+ '@cspell/cspell-resolver@9.7.0':
+ resolution: {integrity: sha512-uiEgS238mdabDnwavo6HXt8K98jlh/jpm7NONroM9NTr9rzck2VZKD2kXEj85wDNMtRsRXNoywTjwQ8WTB6/+w==}
+ engines: {node: '>=20'}
+
+ '@cspell/cspell-service-bus@9.7.0':
+ resolution: {integrity: sha512-fkqtaCkg4jY/FotmzjhIavbXuH0AgUJxZk78Ktf4XlhqOZ4wDeUWrCf220bva4mh3TWiLx/ae9lIlpl59Vx6hA==}
+ engines: {node: '>=20'}
+
+ '@cspell/cspell-types@9.7.0':
+ resolution: {integrity: sha512-Tdfx4eH2uS+gv9V9NCr3Rz+c7RSS6ntXp3Blliud18ibRUlRxO9dTaOjG4iv4x0nAmMeedP1ORkEpeXSkh2QiQ==}
+ engines: {node: '>=20'}
+
+ '@cspell/cspell-worker@9.7.0':
+ resolution: {integrity: sha512-cjEApFF0aOAa1vTUk+e7xP8ofK7iC7hsRzj1FmvvVQz8PoLWPRaq+1bT89ypPsZQvavqm5sIgb97S60/aW4TVg==}
+ engines: {node: '>=20.18'}
+
+ '@cspell/dict-ada@4.1.1':
+ resolution: {integrity: sha512-E+0YW9RhZod/9Qy2gxfNZiHJjCYFlCdI69br1eviQQWB8yOTJX0JHXLs79kOYhSW0kINPVUdvddEBe6Lu6CjGQ==}
+
+ '@cspell/dict-al@1.1.1':
+ resolution: {integrity: sha512-sD8GCaZetgQL4+MaJLXqbzWcRjfKVp8x+px3HuCaaiATAAtvjwUQ5/Iubiqwfd1boIh2Y1/3EgM3TLQ7Q8e0wQ==}
+
+ '@cspell/dict-aws@4.0.17':
+ resolution: {integrity: sha512-ORcblTWcdlGjIbWrgKF+8CNEBQiLVKdUOFoTn0KPNkAYnFcdPP0muT4892h7H4Xafh3j72wqB4/loQ6Nti9E/w==}
+
+ '@cspell/dict-bash@4.2.2':
+ resolution: {integrity: sha512-kyWbwtX3TsCf5l49gGQIZkRLaB/P8g73GDRm41Zu8Mv51kjl2H7Au0TsEvHv7jzcsRLS6aUYaZv6Zsvk1fOz+Q==}
+
+ '@cspell/dict-companies@3.2.11':
+ resolution: {integrity: sha512-0cmafbcz2pTHXLd59eLR1gvDvN6aWAOM0+cIL4LLF9GX9yB2iKDNrKsvs4tJRqutoaTdwNFBbV0FYv+6iCtebQ==}
+
+ '@cspell/dict-cpp@7.0.2':
+ resolution: {integrity: sha512-dfbeERiVNeqmo/npivdR6rDiBCqZi3QtjH2Z0HFcXwpdj6i97dX1xaKyK2GUsO/p4u1TOv63Dmj5Vm48haDpuA==}
+
+ '@cspell/dict-cryptocurrencies@5.0.5':
+ resolution: {integrity: sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==}
+
+ '@cspell/dict-csharp@4.0.8':
+ resolution: {integrity: sha512-qmk45pKFHSxckl5mSlbHxmDitSsGMlk/XzFgt7emeTJWLNSTUK//MbYAkBNRtfzB4uD7pAFiKgpKgtJrTMRnrQ==}
+
+ '@cspell/dict-css@4.1.1':
+ resolution: {integrity: sha512-y/Vgo6qY08e1t9OqR56qjoFLBCpi4QfWMf2qzD1l9omRZwvSMQGRPz4x0bxkkkU4oocMAeztjzCsmLew//c/8w==}
+
+ '@cspell/dict-dart@2.3.2':
+ resolution: {integrity: sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==}
+
+ '@cspell/dict-data-science@2.0.13':
+ resolution: {integrity: sha512-l1HMEhBJkPmw4I2YGVu2eBSKM89K9pVF+N6qIr5Uo5H3O979jVodtuwP8I7LyPrJnC6nz28oxeGRCLh9xC5CVA==}
+
+ '@cspell/dict-django@4.1.6':
+ resolution: {integrity: sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==}
+
+ '@cspell/dict-docker@1.1.17':
+ resolution: {integrity: sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==}
+
+ '@cspell/dict-dotnet@5.0.12':
+ resolution: {integrity: sha512-FiV934kNieIjGTkiApu/WKvLYi/KBpvfWB2TSqpDQtmXZlt3uSa5blwblO1ZC8OvjH8RCq/31H5IdEYmTaZS7A==}
+
+ '@cspell/dict-elixir@4.0.8':
+ resolution: {integrity: sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==}
+
+ '@cspell/dict-en-common-misspellings@2.1.12':
+ resolution: {integrity: sha512-14Eu6QGqyksqOd4fYPuRb58lK1Va7FQK9XxFsRKnZU8LhL3N+kj7YKDW+7aIaAN/0WGEqslGP6lGbQzNti8Akw==}
+
+ '@cspell/dict-en-gb-mit@3.1.20':
+ resolution: {integrity: sha512-rcWEKb1mwgK13CSHu6GwwFA/sWLRkB0twTzSfPxUOULJkhcUrL93ixohk416SBa0BVxixub+lOpTXKcCTbTXsA==}
+
+ '@cspell/dict-en_us@4.4.31':
+ resolution: {integrity: sha512-MdV6mbrSkyIqn9+6F5poCyHIPqWB3H/wlDL9LXfjgAxNFBdYRE767xJtIYunzUqhUiJHSJgZajEFdTtMDw441Q==}
+
+ '@cspell/dict-filetypes@3.0.17':
+ resolution: {integrity: sha512-6f1gZf9o60fGQXGi/fZaryISUNDRmmJwGyr4QU1UvgUgOdpDHLVJtv/0wSL9q5F0wAkYhbXo/fNG8CcUTaf7Ww==}
+
+ '@cspell/dict-flutter@1.1.1':
+ resolution: {integrity: sha512-UlOzRcH2tNbFhZmHJN48Za/2/MEdRHl2BMkCWZBYs+30b91mWvBfzaN4IJQU7dUZtowKayVIF9FzvLZtZokc5A==}
+
+ '@cspell/dict-fonts@4.0.6':
+ resolution: {integrity: sha512-aR/0csY01dNb0A1tw/UmN9rKgHruUxsYsvXu6YlSBJFu60s26SKr/k1o4LavpHTQ+lznlYMqAvuxGkE4Flliqw==}
+
+ '@cspell/dict-fsharp@1.1.1':
+ resolution: {integrity: sha512-imhs0u87wEA4/cYjgzS0tAyaJpwG7vwtC8UyMFbwpmtw+/bgss+osNfyqhYRyS/ehVCWL17Ewx2UPkexjKyaBA==}
+
+ '@cspell/dict-fullstack@3.2.8':
+ resolution: {integrity: sha512-J6EeoeThvx/DFrcA2rJiCA6vfqwJMbkG0IcXhlsmRZmasIpanmxgt90OEaUazbZahFiuJT8wrhgQ1QgD1MsqBw==}
+
+ '@cspell/dict-gaming-terms@1.1.2':
+ resolution: {integrity: sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==}
+
+ '@cspell/dict-git@3.1.0':
+ resolution: {integrity: sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==}
+
+ '@cspell/dict-golang@6.0.26':
+ resolution: {integrity: sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==}
+
+ '@cspell/dict-google@1.0.9':
+ resolution: {integrity: sha512-biL65POqialY0i4g6crj7pR6JnBkbsPovB2WDYkj3H4TuC/QXv7Pu5pdPxeUJA6TSCHI7T5twsO4VSVyRxD9CA==}
+
+ '@cspell/dict-haskell@4.0.6':
+ resolution: {integrity: sha512-ib8SA5qgftExpYNjWhpYIgvDsZ/0wvKKxSP+kuSkkak520iPvTJumEpIE+qPcmJQo4NzdKMN8nEfaeci4OcFAQ==}
+
+ '@cspell/dict-html-symbol-entities@4.0.5':
+ resolution: {integrity: sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==}
+
+ '@cspell/dict-html@4.0.15':
+ resolution: {integrity: sha512-GJYnYKoD9fmo2OI0aySEGZOjThnx3upSUvV7mmqUu8oG+mGgzqm82P/f7OqsuvTaInZZwZbo+PwJQd/yHcyFIw==}
+
+ '@cspell/dict-java@5.0.12':
+ resolution: {integrity: sha512-qPSNhTcl7LGJ5Qp6VN71H8zqvRQK04S08T67knMq9hTA8U7G1sTKzLmBaDOFhq17vNX/+rT+rbRYp+B5Nwza1A==}
+
+ '@cspell/dict-julia@1.1.1':
+ resolution: {integrity: sha512-WylJR9TQ2cgwd5BWEOfdO3zvDB+L7kYFm0I9u0s9jKHWQ6yKmfKeMjU9oXxTBxIufhCXm92SKwwVNAC7gjv+yA==}
+
+ '@cspell/dict-k8s@1.0.12':
+ resolution: {integrity: sha512-2LcllTWgaTfYC7DmkMPOn9GsBWsA4DZdlun4po8s2ysTP7CPEnZc1ZfK6pZ2eI4TsZemlUQQ+NZxMe9/QutQxg==}
+
+ '@cspell/dict-kotlin@1.1.1':
+ resolution: {integrity: sha512-J3NzzfgmxRvEeOe3qUXnSJQCd38i/dpF9/t3quuWh6gXM+krsAXP75dY1CzDmS8mrJAlBdVBeAW5eAZTD8g86Q==}
+
+ '@cspell/dict-latex@5.1.0':
+ resolution: {integrity: sha512-qxT4guhysyBt0gzoliXYEBYinkAdEtR2M7goRaUH0a7ltCsoqqAeEV8aXYRIdZGcV77gYSobvu3jJL038tlPAw==}
+
+ '@cspell/dict-lorem-ipsum@4.0.5':
+ resolution: {integrity: sha512-9a4TJYRcPWPBKkQAJ/whCu4uCAEgv/O2xAaZEI0n4y1/l18Yyx8pBKoIX5QuVXjjmKEkK7hi5SxyIsH7pFEK9Q==}
+
+ '@cspell/dict-lua@4.0.8':
+ resolution: {integrity: sha512-N4PkgNDMu9JVsRu7JBS/3E/dvfItRgk9w5ga2dKq+JupP2Y3lojNaAVFhXISh4Y0a6qXDn2clA6nvnavQ/jjLA==}
+
+ '@cspell/dict-makefile@1.0.5':
+ resolution: {integrity: sha512-4vrVt7bGiK8Rx98tfRbYo42Xo2IstJkAF4tLLDMNQLkQ86msDlYSKG1ZCk8Abg+EdNcFAjNhXIiNO+w4KflGAQ==}
+
+ '@cspell/dict-markdown@2.0.16':
+ resolution: {integrity: sha512-976RRqKv6cwhrxdFCQP2DdnBVB86BF57oQtPHy4Zbf4jF/i2Oy29MCrxirnOBalS1W6KQeto7NdfDXRAwkK4PQ==}
+ peerDependencies:
+ '@cspell/dict-css': ^4.1.1
+ '@cspell/dict-html': ^4.0.15
+ '@cspell/dict-html-symbol-entities': ^4.0.5
+ '@cspell/dict-typescript': ^3.2.3
+
+ '@cspell/dict-monkeyc@1.0.12':
+ resolution: {integrity: sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==}
+
+ '@cspell/dict-node@5.0.9':
+ resolution: {integrity: sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==}
+
+ '@cspell/dict-npm@5.2.37':
+ resolution: {integrity: sha512-dYKrD0bI08YgebJcbjsIDpTMK6EH0wTkEyQLsaH0T4tmsLJE95coTYb3eE7giRRGJADvBqrjH4fz5+INd85QqQ==}
+
+ '@cspell/dict-php@4.1.1':
+ resolution: {integrity: sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==}
+
+ '@cspell/dict-powershell@5.0.15':
+ resolution: {integrity: sha512-l4S5PAcvCFcVDMJShrYD0X6Huv9dcsQPlsVsBGbH38wvuN7gS7+GxZFAjTNxDmTY1wrNi1cCatSg6Pu2BW4rgg==}
+
+ '@cspell/dict-public-licenses@2.0.16':
+ resolution: {integrity: sha512-EQRrPvEOmwhwWezV+W7LjXbIBjiy6y/shrET6Qcpnk3XANTzfvWflf9PnJ5kId/oKWvihFy0za0AV1JHd03pSQ==}
+
+ '@cspell/dict-python@4.2.25':
+ resolution: {integrity: sha512-hDdN0YhKgpbtZVRjQ2c8jk+n0wQdidAKj1Fk8w7KEHb3YlY5uPJ0mAKJk7AJKPNLOlILoUmN+HAVJz+cfSbWYg==}
+
+ '@cspell/dict-r@2.1.1':
+ resolution: {integrity: sha512-71Ka+yKfG4ZHEMEmDxc6+blFkeTTvgKbKAbwiwQAuKl3zpqs1Y0vUtwW2N4b3LgmSPhV3ODVY0y4m5ofqDuKMw==}
+
+ '@cspell/dict-ruby@5.1.1':
+ resolution: {integrity: sha512-LHrp84oEV6q1ZxPPyj4z+FdKyq1XAKYPtmGptrd+uwHbrF/Ns5+fy6gtSi7pS+uc0zk3JdO9w/tPK+8N1/7WUA==}
+
+ '@cspell/dict-rust@4.1.2':
+ resolution: {integrity: sha512-O1FHrumYcO+HZti3dHfBPUdnDFkI+nbYK3pxYmiM1sr+G0ebOd6qchmswS0Wsc6ZdEVNiPYJY/gZQR6jfW3uOg==}
+
+ '@cspell/dict-scala@5.0.9':
+ resolution: {integrity: sha512-AjVcVAELgllybr1zk93CJ5wSUNu/Zb5kIubymR/GAYkMyBdYFCZ3Zbwn4Zz8GJlFFAbazABGOu0JPVbeY59vGg==}
+
+ '@cspell/dict-shell@1.1.2':
+ resolution: {integrity: sha512-WqOUvnwcHK1X61wAfwyXq04cn7KYyskg90j4lLg3sGGKMW9Sq13hs91pqrjC44Q+lQLgCobrTkMDw9Wyl9nRFA==}
+
+ '@cspell/dict-software-terms@5.2.0':
+ resolution: {integrity: sha512-jyucc8KKxH5ClC4FPICISgcKAZU7UwgFdPkuuuK5nYIw0+l1umnUSn9IKCcOaurvXujvVP6mBfclXpUtmT6Vrw==}
+
+ '@cspell/dict-sql@2.2.1':
+ resolution: {integrity: sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==}
+
+ '@cspell/dict-svelte@1.0.7':
+ resolution: {integrity: sha512-hGZsGqP0WdzKkdpeVLBivRuSNzOTvN036EBmpOwxH+FTY2DuUH7ecW+cSaMwOgmq5JFSdTcbTNFlNC8HN8lhaQ==}
+
+ '@cspell/dict-swift@2.0.6':
+ resolution: {integrity: sha512-PnpNbrIbex2aqU1kMgwEKvCzgbkHtj3dlFLPMqW1vSniop7YxaDTtvTUO4zA++ugYAEL+UK8vYrBwDPTjjvSnA==}
+
+ '@cspell/dict-terraform@1.1.3':
+ resolution: {integrity: sha512-gr6wxCydwSFyyBKhBA2xkENXtVFToheqYYGFvlMZXWjviynXmh+NK/JTvTCk/VHk3+lzbO9EEQKee6VjrAUSbA==}
+
+ '@cspell/dict-typescript@3.2.3':
+ resolution: {integrity: sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==}
+
+ '@cspell/dict-vue@3.0.5':
+ resolution: {integrity: sha512-Mqutb8jbM+kIcywuPQCCaK5qQHTdaByoEO2J9LKFy3sqAdiBogNkrplqUK0HyyRFgCfbJUgjz3N85iCMcWH0JA==}
+
+ '@cspell/dict-zig@1.0.0':
+ resolution: {integrity: sha512-XibBIxBlVosU06+M6uHWkFeT0/pW5WajDRYdXG2CgHnq85b0TI/Ks0FuBJykmsgi2CAD3Qtx8UHFEtl/DSFnAQ==}
+
+ '@cspell/dynamic-import@9.7.0':
+ resolution: {integrity: sha512-Ws36IYvtS/8IN3x6K9dPLvTmaArodRJmzTn2Rkf2NaTnIYWhRuFzsP3SVVO59NN3fXswAEbmz5DSbVUe8bPZHg==}
+ engines: {node: '>=20'}
+
+ '@cspell/filetypes@9.7.0':
+ resolution: {integrity: sha512-Ln9e/8wGOyTeL3DCCs6kwd18TSpTw3kxsANjTrzLDASrX4cNmAdvc9J5dcIuBHPaqOAnRQxuZbzUlpRh73Y24w==}
+ engines: {node: '>=20'}
+
+ '@cspell/rpc@9.7.0':
+ resolution: {integrity: sha512-VnZ4ABgQeoS4RwofcePkDP7L6tf3Kh5D7LQKoyRM4R6XtfSsYefym6XKaRl3saGtthH5YyjgNJ0Tgdjen4wAAw==}
+ engines: {node: '>=20.18'}
+
+ '@cspell/strong-weak-map@9.7.0':
+ resolution: {integrity: sha512-5xbvDASjklrmy88O6gmGXgYhpByCXqOj5wIgyvwZe2l83T1bE+iOfGI4pGzZJ/mN+qTn1DNKq8BPBPtDgb7Q2Q==}
+ engines: {node: '>=20'}
+
+ '@cspell/url@9.7.0':
+ resolution: {integrity: sha512-ZaaBr0pTvNxmyUbIn+nVPXPr383VqJzfUDMWicgTjJIeo2+T2hOq2kNpgpvTIrWtZrsZnSP8oXms1+sKTjcvkw==}
+ engines: {node: '>=20'}
+
+ '@cspotcode/source-map-support@0.8.1':
+ resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+ engines: {node: '>=12'}
+
+ '@csstools/color-helpers@5.1.0':
+ resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
+ engines: {node: '>=18'}
+
+ '@csstools/css-calc@2.1.4':
+ resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-color-parser@3.1.0':
+ resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-tokenizer@3.0.4':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+ engines: {node: '>=18'}
+
+ '@emnapi/core@1.8.1':
+ resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
+
+ '@emnapi/runtime@1.8.1':
+ resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
+
+ '@emnapi/wasi-threads@1.1.0':
+ resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
+
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@eslint-community/eslint-utils@4.9.1':
+ resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.2':
+ resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.21.2':
+ resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.4.2':
+ resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.17.0':
+ resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.5':
+ resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.39.4':
+ resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.7':
+ resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.4.1':
+ resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.7':
+ resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@img/colour@1.1.0':
+ resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.34.5':
+ resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.34.5':
+ resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.34.5':
+ resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.34.5':
+ resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.34.5':
+ resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.34.5':
+ resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.34.5':
+ resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.34.5':
+ resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.34.5':
+ resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@jridgewell/trace-mapping@0.3.9':
+ resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+ '@napi-rs/wasm-runtime@0.2.12':
+ resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
+
+ '@next/env@15.4.10':
+ resolution: {integrity: sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg==}
+
+ '@next/eslint-plugin-next@15.4.1':
+ resolution: {integrity: sha512-lQnHUxN7mMksK7IxgKDIXNMWFOBmksVrjamMEURXiYfo7zgsc30lnU8u4y/MJktSh+nB80ktTQeQbWdQO6c8Ow==}
+
+ '@next/eslint-plugin-next@15.5.12':
+ resolution: {integrity: sha512-+ZRSDFTv4aC96aMb5E41rMjysx8ApkryevnvEYZvPZO52KvkqP5rNExLUXJFr9P4s0f3oqNQR6vopCZsPWKDcQ==}
+
+ '@next/swc-darwin-arm64@15.4.8':
+ resolution: {integrity: sha512-Pf6zXp7yyQEn7sqMxur6+kYcywx5up1J849psyET7/8pG2gQTVMjU3NzgIt8SeEP5to3If/SaWmaA6H6ysBr1A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@15.4.8':
+ resolution: {integrity: sha512-xla6AOfz68a6kq3gRQccWEvFC/VRGJmA/QuSLENSO7CZX5WIEkSz7r1FdXUjtGCQ1c2M+ndUAH7opdfLK1PQbw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@15.4.8':
+ resolution: {integrity: sha512-y3fmp+1Px/SJD+5ntve5QLZnGLycsxsVPkTzAc3zUiXYSOlTPqT8ynfmt6tt4fSo1tAhDPmryXpYKEAcoAPDJw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-arm64-musl@15.4.8':
+ resolution: {integrity: sha512-DX/L8VHzrr1CfwaVjBQr3GWCqNNFgyWJbeQ10Lx/phzbQo3JNAxUok1DZ8JHRGcL6PgMRgj6HylnLNndxn4Z6A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-x64-gnu@15.4.8':
+ resolution: {integrity: sha512-9fLAAXKAL3xEIFdKdzG5rUSvSiZTLLTCc6JKq1z04DR4zY7DbAPcRvNm3K1inVhTiQCs19ZRAgUerHiVKMZZIA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-linux-x64-musl@15.4.8':
+ resolution: {integrity: sha512-s45V7nfb5g7dbS7JK6XZDcapicVrMMvX2uYgOHP16QuKH/JA285oy6HcxlKqwUNaFY/UC6EvQ8QZUOo19cBKSA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-win32-arm64-msvc@15.4.8':
+ resolution: {integrity: sha512-KjgeQyOAq7t/HzAJcWPGA8X+4WY03uSCZ2Ekk98S9OgCFsb6lfBE3dbUzUuEQAN2THbwYgFfxX2yFTCMm8Kehw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@15.4.8':
+ resolution: {integrity: sha512-Exsmf/+42fWVnLMaZHzshukTBxZrSwuuLKFvqhGHJ+mC1AokqieLY/XzAl3jc/CqhXLqLY3RRjkKJ9YnLPcRWg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@noble/ciphers@1.3.0':
+ resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==}
+ engines: {node: ^14.21.3 || >=16}
+
+ '@noble/curves@1.2.0':
+ resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==}
+
+ '@noble/curves@1.9.1':
+ resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==}
+ engines: {node: ^14.21.3 || >=16}
+
+ '@noble/hashes@1.3.2':
+ resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==}
+ engines: {node: '>= 16'}
+
+ '@noble/hashes@1.8.0':
+ resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
+ engines: {node: ^14.21.3 || >=16}
+
+ '@noble/secp256k1@2.3.0':
+ resolution: {integrity: sha512-0TQed2gcBbIrh7Ccyw+y/uZQvbJwm7Ao4scBUxqpBCcsOlZG0O4KGfjtNAy/li4W8n1xt3dxrwJ0beZ2h2G6Kw==}
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@nolyfill/is-core-module@1.0.39':
+ resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
+ engines: {node: '>=12.4.0'}
+
+ '@opentelemetry/api@1.9.0':
+ resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
+ engines: {node: '>=8.0.0'}
+
+ '@prisma/client@6.19.2':
+ resolution: {integrity: sha512-gR2EMvfK/aTxsuooaDA32D8v+us/8AAet+C3J1cc04SW35FPdZYgLF+iN4NDLUgAaUGTKdAB0CYenu1TAgGdMg==}
+ engines: {node: '>=18.18'}
+ peerDependencies:
+ prisma: '*'
+ typescript: '>=5.1.0'
+ peerDependenciesMeta:
+ prisma:
+ optional: true
+ typescript:
+ optional: true
+
+ '@prisma/config@6.19.2':
+ resolution: {integrity: sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ==}
+
+ '@prisma/debug@6.19.2':
+ resolution: {integrity: sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==}
+
+ '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7':
+ resolution: {integrity: sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==}
+
+ '@prisma/engines@6.19.2':
+ resolution: {integrity: sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A==}
+
+ '@prisma/fetch-engine@6.19.2':
+ resolution: {integrity: sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q==}
+
+ '@prisma/get-platform@6.19.2':
+ resolution: {integrity: sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA==}
+
+ '@rollup/rollup-android-arm-eabi@4.59.0':
+ resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.59.0':
+ resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.59.0':
+ resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.59.0':
+ resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.59.0':
+ resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.59.0':
+ resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.59.0':
+ resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.59.0':
+ resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.59.0':
+ resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.59.0':
+ resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.59.0':
+ resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-musl@4.59.0':
+ resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.59.0':
+ resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-musl@4.59.0':
+ resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.59.0':
+ resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.59.0':
+ resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.59.0':
+ resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.59.0':
+ resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.59.0':
+ resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openbsd-x64@4.59.0':
+ resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.59.0':
+ resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.59.0':
+ resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.59.0':
+ resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.59.0':
+ resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.59.0':
+ resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rtsao/scc@1.1.0':
+ resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+
+ '@rushstack/eslint-patch@1.16.1':
+ resolution: {integrity: sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==}
+
+ '@scure/base@1.2.6':
+ resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==}
+
+ '@scure/bip32@1.7.0':
+ resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==}
+
+ '@scure/bip39@1.6.0':
+ resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==}
+
+ '@spruceid/siwe-parser@2.1.2':
+ resolution: {integrity: sha512-d/r3S1LwJyMaRAKQ0awmo9whfXeE88Qt00vRj91q5uv5ATtWIQEGJ67Yr5eSZw5zp1/fZCXZYuEckt8lSkereQ==}
+
+ '@stablelib/binary@1.0.1':
+ resolution: {integrity: sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==}
+
+ '@stablelib/int@1.0.1':
+ resolution: {integrity: sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==}
+
+ '@stablelib/random@1.0.2':
+ resolution: {integrity: sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==}
+
+ '@stablelib/wipe@1.0.1':
+ resolution: {integrity: sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@supabase/auth-js@2.99.0':
+ resolution: {integrity: sha512-tHiIST/OEoLmWBE+3X69xRY5srJM/lL86KltmMlIfDo9ePJLo14vQQV9T4NF+P+MoGhCwQL1GTmk51zuAFMXKw==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/functions-js@2.99.0':
+ resolution: {integrity: sha512-zA9oad6EqGwMLLu2LfP1bXbqKcJGiotAdbdTfZG7YS7619YZQAEgejj9mp+E5vglKE1yMWbKK+S1J3PbuUtgLg==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/postgrest-js@2.99.0':
+ resolution: {integrity: sha512-8qfOMi2pu9y0IQhUAeFqjrvR49G4ELGevXCWV9qAHXFQ/h2FFh0I8PYjFQj4rHcHSq6hrpozDnS1vbQU8NAQ/A==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/realtime-js@2.99.0':
+ resolution: {integrity: sha512-7nFTZhNeANR7FvEY6PfWLCfE8dHqcaJd9SuR7IPEZvBPG9K4uEHMivpjZx4NWRSU7Eji7ZbKy2LG+cJ48DhwHg==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/storage-js@2.99.0':
+ resolution: {integrity: sha512-mAEEbfsght5EEALejYrwAP9k8sFBGjfMZT8n4SyMXk2iYuWVeRMs1kA/uKg0uDMctWdZ0bL+L4jZzksUJpCjMA==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/supabase-js@2.99.0':
+ resolution: {integrity: sha512-SP9Sn9tsHDB7N4u2gT13rdeZJewE4xibAxasG7vOz+fYi92+XkMMbWNx0uGK53zKTnAnvTs16isRooyBy4sn5w==}
+ engines: {node: '>=20.0.0'}
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@testing-library/jest-dom@6.9.1':
+ resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==}
+ engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
+
+ '@tsconfig/node10@1.0.12':
+ resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==}
+
+ '@tsconfig/node12@1.0.11':
+ resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
+ '@tsconfig/node14@1.0.3':
+ resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
+ '@tsconfig/node16@1.0.4':
+ resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
+ '@tybys/wasm-util@0.10.1':
+ resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
+
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/json5@0.0.29':
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+
+ '@types/node@22.7.5':
+ resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==}
+
+ '@types/node@24.12.0':
+ resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==}
+
+ '@types/phoenix@1.6.7':
+ resolution: {integrity: sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==}
+
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.14':
+ resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
+
+ '@types/ws@8.18.1':
+ resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+
+ '@typescript-eslint/eslint-plugin@8.57.0':
+ resolution: {integrity: sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.57.0
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/parser@8.57.0':
+ resolution: {integrity: sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/project-service@8.57.0':
+ resolution: {integrity: sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/scope-manager@8.57.0':
+ resolution: {integrity: sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.57.0':
+ resolution: {integrity: sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/type-utils@8.57.0':
+ resolution: {integrity: sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/types@8.57.0':
+ resolution: {integrity: sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.57.0':
+ resolution: {integrity: sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/utils@8.57.0':
+ resolution: {integrity: sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/visitor-keys@8.57.0':
+ resolution: {integrity: sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@unrs/resolver-binding-android-arm-eabi@1.11.1':
+ resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
+ cpu: [arm]
+ os: [android]
+
+ '@unrs/resolver-binding-android-arm64@1.11.1':
+ resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==}
+ cpu: [arm64]
+ os: [android]
+
+ '@unrs/resolver-binding-darwin-arm64@1.11.1':
+ resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-darwin-x64@1.11.1':
+ resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-freebsd-x64@1.11.1':
+ resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
+ resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
+ resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
+ resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
+ resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
+ resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
+ resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
+ resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
+ resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
+ resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
+ cpu: [x64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-x64-musl@1.11.1':
+ resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@unrs/resolver-binding-wasm32-wasi@1.11.1':
+ resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
+ resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
+ resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
+ resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==}
+ cpu: [x64]
+ os: [win32]
+
+ '@vercel/analytics@1.6.1':
+ resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==}
+ peerDependencies:
+ '@remix-run/react': ^2
+ '@sveltejs/kit': ^1 || ^2
+ next: '>= 13'
+ react: ^18 || ^19 || ^19.0.0-rc
+ svelte: '>= 4'
+ vue: ^3
+ vue-router: ^4
+ peerDependenciesMeta:
+ '@remix-run/react':
+ optional: true
+ '@sveltejs/kit':
+ optional: true
+ next:
+ optional: true
+ react:
+ optional: true
+ svelte:
+ optional: true
+ vue:
+ optional: true
+ vue-router:
+ optional: true
+
+ '@vitest/expect@3.2.4':
+ resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
+
+ '@vitest/mocker@3.2.4':
+ resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@3.2.4':
+ resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
+
+ '@vitest/runner@3.2.4':
+ resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
+
+ '@vitest/snapshot@3.2.4':
+ resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
+
+ '@vitest/spy@3.2.4':
+ resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
+
+ '@vitest/utils@3.2.4':
+ resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
+
+ '@worldcoin/agentkit@0.0.3':
+ resolution: {integrity: sha512-WjvAX27mGj7rrC4LPxkSHkj56JnAj41Hfak5ScsVJcRin7Aty6Y3NqtUmWeso/GdOW/tqFHrHQ6EWKhnG23euA==}
+
+ '@worldcoin/idkit-core@4.0.11':
+ resolution: {integrity: sha512-Fu4oHavbMCS0N8mQrYDpbZ9GZEYJjcAgotcbNw/l+NcuFIDKbo8xleTVz/icyQAaaT0QMkxtagrrH7kTKppG5w==}
+
+ '@worldcoin/idkit-server@1.0.0':
+ resolution: {integrity: sha512-ZCyXJFexxLjPtG7lxTntf6oiwF0pzoUIsVFIAAgFyXav5KmUnVlrvVoeGrgpiDFRznH2B4/Iv5xv+SkEHxOdXQ==}
+ engines: {node: '>=18'}
+
+ '@worldcoin/idkit@4.0.8':
+ resolution: {integrity: sha512-F3QH1P7oxHPT3syXuVuWsfvt7tAD+mJVBtV6yxT684Zv+NBMLCCwLh7f9Lk7tItlKXKXwFIwJRGJmBgECJcpiQ==}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
+ '@x402/core@2.6.0':
+ resolution: {integrity: sha512-ISC/JeVss6xlKvor2rp18tJf9K5OQlIDDfZW1VZJQGDI2F4gy+HWxxkFfcQalCsPp4YUlwqh0YOkUxP+LTZWVg==}
+
+ abitype@1.2.3:
+ resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==}
+ peerDependencies:
+ typescript: '>=5.0.4'
+ zod: ^3.22.0 || ^4.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ zod:
+ optional: true
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn-walk@8.3.5:
+ resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
+ engines: {node: '>=0.4.0'}
+
+ acorn@8.16.0:
+ resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ aes-js@4.0.0-beta.5:
+ resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==}
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ ajv@6.14.0:
+ resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ apg-js@4.4.0:
+ resolution: {integrity: sha512-fefmXFknJmtgtNEXfPwZKYkMFX4Fyeyz+fNF6JWp87biGOPslJbCBVU158zvKRZfHBKnJDy8CMM40oLFGkXT8Q==}
+
+ arg@4.1.3:
+ resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+
+ arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
+ array-buffer-byte-length@1.0.2:
+ resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+ engines: {node: '>= 0.4'}
+
+ array-includes@3.1.9:
+ resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
+ engines: {node: '>= 0.4'}
+
+ array-timsort@1.0.3:
+ resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
+
+ array.prototype.findlast@1.2.5:
+ resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlastindex@1.2.6:
+ resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flat@1.3.3:
+ resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flatmap@1.3.3:
+ resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.tosorted@1.1.4:
+ resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+ engines: {node: '>= 0.4'}
+
+ arraybuffer.prototype.slice@1.0.4:
+ resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ engines: {node: '>= 0.4'}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ ast-types-flow@0.0.8:
+ resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
+
+ async-function@1.0.0:
+ resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ engines: {node: '>= 0.4'}
+
+ autoprefixer@10.4.27:
+ resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ axe-core@4.11.1:
+ resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==}
+ engines: {node: '>=4'}
+
+ axobject-query@4.1.0:
+ resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+ engines: {node: '>= 0.4'}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
+ baseline-browser-mapping@2.10.0:
+ resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ binary-extensions@2.3.0:
+ resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+ engines: {node: '>=8'}
+
+ brace-expansion@1.1.12:
+ resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+
+ brace-expansion@5.0.4:
+ resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==}
+ engines: {node: 18 || 20 || >=22}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ c12@3.1.0:
+ resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==}
+ peerDependencies:
+ magicast: ^0.3.5
+ peerDependenciesMeta:
+ magicast:
+ optional: true
+
+ cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.8:
+ resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ camelcase-css@2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+
+ camelcase@5.3.1:
+ resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
+ engines: {node: '>=6'}
+
+ caniuse-lite@1.0.30001777:
+ resolution: {integrity: sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==}
+
+ chai@5.3.3:
+ resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
+ engines: {node: '>=18'}
+
+ chalk-template@1.1.2:
+ resolution: {integrity: sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==}
+ engines: {node: '>=14.16'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ chalk@5.6.2:
+ resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ check-error@2.1.3:
+ resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
+ engines: {node: '>= 16'}
+
+ chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+
+ chokidar@4.0.3:
+ resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+ engines: {node: '>= 14.16.0'}
+
+ citty@0.1.6:
+ resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
+
+ citty@0.2.1:
+ resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==}
+
+ clear-module@4.1.2:
+ resolution: {integrity: sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==}
+ engines: {node: '>=8'}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ cliui@6.0.0:
+ resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ commander@14.0.3:
+ resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
+ engines: {node: '>=20'}
+
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
+ comment-json@4.6.2:
+ resolution: {integrity: sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==}
+ engines: {node: '>= 6'}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ confbox@0.2.4:
+ resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
+
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
+
+ create-require@1.1.1:
+ resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ cspell-config-lib@9.7.0:
+ resolution: {integrity: sha512-pguh8A3+bSJ1OOrKCiQan8bvaaY125de76OEFz7q1Pq309lIcDrkoL/W4aYbso/NjrXaIw6OjkgPMGRBI/IgGg==}
+ engines: {node: '>=20'}
+
+ cspell-dictionary@9.7.0:
+ resolution: {integrity: sha512-k/Wz0so32+0QEqQe21V9m4BNXM5ZN6lz3Ix/jLCbMxFIPl6wT711ftjOWIEMFhvUOP0TWXsbzcuE9mKtS5mTig==}
+ engines: {node: '>=20'}
+
+ cspell-gitignore@9.7.0:
+ resolution: {integrity: sha512-MtoYuH4ah4K6RrmaF834npMcRsTKw0658mC6yvmBacUQOmwB/olqyuxF3fxtbb55HDb7cXDQ35t1XuwwGEQeZw==}
+ engines: {node: '>=20'}
+ hasBin: true
+
+ cspell-glob@9.7.0:
+ resolution: {integrity: sha512-LUeAoEsoCJ+7E3TnUmWBscpVQOmdwBejMlFn0JkXy6LQzxrybxXBKf65RSdIv1o5QtrhQIMa358xXYQG0sv/tA==}
+ engines: {node: '>=20'}
+
+ cspell-grammar@9.7.0:
+ resolution: {integrity: sha512-oEYME+7MJztfVY1C06aGcJgEYyqBS/v/ETkQGPzf/c6ObSAPRcUbVtsXZgnR72Gru9aBckc70xJcD6bELdoWCA==}
+ engines: {node: '>=20'}
+ hasBin: true
+
+ cspell-io@9.7.0:
+ resolution: {integrity: sha512-V7x0JHAUCcJPRCH8c0MQkkaKmZD2yotxVyrNEx2SZTpvnKrYscLEnUUTWnGJIIf9znzISqw116PLnYu2c+zd6Q==}
+ engines: {node: '>=20'}
+
+ cspell-lib@9.7.0:
+ resolution: {integrity: sha512-aTx/aLRpnuY1RJnYAu+A8PXfm1oIUdvAQ4W9E66bTgp1LWI+2G2++UtaPxRIgI0olxE9vcXqUnKpjOpO+5W9bQ==}
+ engines: {node: '>=20'}
+
+ cspell-trie-lib@9.7.0:
+ resolution: {integrity: sha512-a2YqmcraL3g6I/4gY7SYWEZfP73oLluUtxO7wxompk/kOG2K1FUXyQfZXaaR7HxVv10axT1+NrjhOmXpfbI6LA==}
+ engines: {node: '>=20'}
+ peerDependencies:
+ '@cspell/cspell-types': 9.7.0
+
+ cspell@9.7.0:
+ resolution: {integrity: sha512-ftxOnkd+scAI7RZ1/ksgBZRr0ouC7QRKtPQhD/PbLTKwAM62sSvRhE1bFsuW3VKBn/GilWzTjkJ40WmnDqH5iQ==}
+ engines: {node: '>=20.18'}
+ hasBin: true
+
+ css.escape@1.5.1:
+ resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
+
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ cssstyle@4.6.0:
+ resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
+ engines: {node: '>=18'}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ damerau-levenshtein@1.0.8:
+ resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
+
+ data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+
+ data-view-buffer@1.0.2:
+ resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-length@1.0.2:
+ resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-offset@1.0.1:
+ resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ engines: {node: '>= 0.4'}
+
+ debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decamelize@1.2.0:
+ resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
+ engines: {node: '>=0.10.0'}
+
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
+ deep-eql@5.0.2:
+ resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
+ engines: {node: '>=6'}
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ deepmerge-ts@7.1.5:
+ resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==}
+ engines: {node: '>=16.0.0'}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ defu@6.1.4:
+ resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+
+ destr@2.0.5:
+ resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ didyoumean@1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+
+ diff@4.0.4:
+ resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==}
+ engines: {node: '>=0.3.1'}
+
+ dijkstrajs@1.0.3:
+ resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
+
+ dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
+ doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+
+ dom-accessibility-api@0.6.3:
+ resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
+
+ dotenv@16.6.1:
+ resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
+ engines: {node: '>=12'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ effect@3.18.4:
+ resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==}
+
+ electron-to-chromium@1.5.307:
+ resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ empathic@2.0.0:
+ resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
+ engines: {node: '>=14'}
+
+ entities@6.0.1:
+ resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+ engines: {node: '>=0.12'}
+
+ env-paths@4.0.0:
+ resolution: {integrity: sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==}
+ engines: {node: '>=20'}
+
+ es-abstract@1.24.1:
+ resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
+ engines: {node: '>= 0.4'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-iterator-helpers@1.2.2:
+ resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==}
+ engines: {node: '>= 0.4'}
+
+ es-module-lexer@1.7.0:
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ es-shim-unscopables@1.1.0:
+ resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+ engines: {node: '>= 0.4'}
+
+ es-to-primitive@1.3.0:
+ resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+ engines: {node: '>= 0.4'}
+
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-config-next@15.4.1:
+ resolution: {integrity: sha512-XIIN+lq8XuSwXUrcv+0uHMDFGJFPxLAw04/a4muFZYygSvStvVa15nY7kh4Il6yOVJyxdMUyVdQ9ApGedaeupw==}
+ peerDependencies:
+ eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
+ typescript: '>=3.3.1'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ eslint-import-resolver-node@0.3.9:
+ resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+
+ eslint-import-resolver-typescript@3.10.1:
+ resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '*'
+ eslint-plugin-import: '*'
+ eslint-plugin-import-x: '*'
+ peerDependenciesMeta:
+ eslint-plugin-import:
+ optional: true
+ eslint-plugin-import-x:
+ optional: true
+
+ eslint-module-utils@2.12.1:
+ resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+
+ eslint-plugin-boundaries@5.4.0:
+ resolution: {integrity: sha512-6SQmEhXCqGrrxm9YiM24SC95CqrVi2MUOm5SDrfquceh/os8MIAvZYsDU69zvtCSb1S6UbNEmdioi1gCDc8+VQ==}
+ engines: {node: '>=18.18'}
+ peerDependencies:
+ eslint: '>=6.0.0'
+
+ eslint-plugin-import@2.32.0:
+ resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+
+ eslint-plugin-jsx-a11y@6.10.2:
+ resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
+
+ eslint-plugin-react-hooks@5.2.0:
+ resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+
+ eslint-plugin-react@7.37.5:
+ resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+
+ eslint-plugin-tailwindcss@3.18.2:
+ resolution: {integrity: sha512-QbkMLDC/OkkjFQ1iz/5jkMdHfiMu/uwujUHLAJK5iwNHD8RTxVTlsUezE0toTZ6VhybNBsk+gYGPDq2agfeRNA==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ tailwindcss: ^3.4.0
+
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@5.0.1:
+ resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ eslint@9.39.4:
+ resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esprima@4.0.1:
+ resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ esquery@1.7.0:
+ resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ ethers@6.16.0:
+ resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==}
+ engines: {node: '>=14.0.0'}
+
+ eventemitter3@5.0.1:
+ resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
+
+ expect-type@1.3.0:
+ resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
+ engines: {node: '>=12.0.0'}
+
+ exsolve@1.0.8:
+ resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
+
+ fast-check@3.23.2:
+ resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==}
+ engines: {node: '>=8.0.0'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-equals@6.0.0:
+ resolution: {integrity: sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==}
+ engines: {node: '>=6.0.0'}
+
+ fast-glob@3.3.1:
+ resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fastq@1.20.1:
+ resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-up@4.1.0:
+ resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.4.1:
+ resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==}
+
+ for-each@0.3.5:
+ resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+ engines: {node: '>= 0.4'}
+
+ fraction.js@5.3.4:
+ resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ function.prototype.name@1.1.8:
+ resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
+ engines: {node: '>= 0.4'}
+
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ generator-function@2.0.1:
+ resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
+ engines: {node: '>= 0.4'}
+
+ gensequence@8.0.8:
+ resolution: {integrity: sha512-omMVniXEXpdx/vKxGnPRoO2394Otlze28TyxECbFVyoSpZ9H3EO7lemjcB12OpQJzRW4e5tt/dL1rOxry6aMHg==}
+ engines: {node: '>=20'}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ get-symbol-description@1.1.0:
+ resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+ engines: {node: '>= 0.4'}
+
+ get-tsconfig@4.13.6:
+ resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
+
+ giget@2.0.0:
+ resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==}
+ hasBin: true
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ global-directory@5.0.0:
+ resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==}
+ engines: {node: '>=20'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ handlebars@4.7.8:
+ resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
+ engines: {node: '>=0.4.7'}
+ hasBin: true
+
+ has-bigints@1.1.0:
+ resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ engines: {node: '>= 0.4'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.2.0:
+ resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ iceberg-js@0.8.1:
+ resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==}
+ engines: {node: '>=20.0.0'}
+
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ ignore@7.0.5:
+ resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ import-meta-resolve@4.2.0:
+ resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
+ ini@6.0.0:
+ resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+
+ internal-slot@1.1.0:
+ resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+ engines: {node: '>= 0.4'}
+
+ is-array-buffer@3.0.5:
+ resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+ engines: {node: '>= 0.4'}
+
+ is-async-function@2.1.1:
+ resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+ engines: {node: '>= 0.4'}
+
+ is-bigint@1.1.0:
+ resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ engines: {node: '>= 0.4'}
+
+ is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+
+ is-boolean-object@1.2.2:
+ resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ engines: {node: '>= 0.4'}
+
+ is-bun-module@2.0.0:
+ resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
+
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ is-data-view@1.0.2:
+ resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+ engines: {node: '>= 0.4'}
+
+ is-date-object@1.1.0:
+ resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-finalizationregistry@1.1.1:
+ resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+ engines: {node: '>= 0.4'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-generator-function@1.1.2:
+ resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
+ engines: {node: '>= 0.4'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-map@2.0.3:
+ resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+ engines: {node: '>= 0.4'}
+
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+
+ is-number-object@1.1.1:
+ resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+ engines: {node: '>= 0.4'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
+ is-regex@1.2.1:
+ resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+ engines: {node: '>= 0.4'}
+
+ is-safe-filename@0.1.1:
+ resolution: {integrity: sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==}
+ engines: {node: '>=20'}
+
+ is-set@2.0.3:
+ resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+ engines: {node: '>= 0.4'}
+
+ is-shared-array-buffer@1.0.4:
+ resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+ engines: {node: '>= 0.4'}
+
+ is-string@1.1.1:
+ resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+ engines: {node: '>= 0.4'}
+
+ is-symbol@1.1.1:
+ resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+ engines: {node: '>= 0.4'}
+
+ is-typed-array@1.1.15:
+ resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+ engines: {node: '>= 0.4'}
+
+ is-weakmap@2.0.2:
+ resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+ engines: {node: '>= 0.4'}
+
+ is-weakref@1.1.1:
+ resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+ engines: {node: '>= 0.4'}
+
+ is-weakset@2.0.4:
+ resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ engines: {node: '>= 0.4'}
+
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ isows@1.0.7:
+ resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==}
+ peerDependencies:
+ ws: '*'
+
+ iterator.prototype@1.1.5:
+ resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+ engines: {node: '>= 0.4'}
+
+ jiti@1.21.7:
+ resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
+ hasBin: true
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-tokens@9.0.1:
+ resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+
+ js-yaml@4.1.1:
+ resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+ hasBin: true
+
+ jsdom@26.1.0:
+ resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+
+ jsx-ast-utils@3.3.5:
+ resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+ engines: {node: '>=4.0'}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ language-subtag-registry@0.3.23:
+ resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
+
+ language-tags@1.0.9:
+ resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
+ engines: {node: '>=0.10'}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ locate-path@5.0.0:
+ resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+ engines: {node: '>=8'}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ loupe@3.2.1:
+ resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ make-error@1.3.6:
+ resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ min-indent@1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+
+ minimatch@10.2.4:
+ resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
+ engines: {node: 18 || 20 || >=22}
+
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ napi-postinstall@0.3.4:
+ resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ neo-async@2.6.2:
+ resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+
+ next@15.4.10:
+ resolution: {integrity: sha512-itVlc79QjpKMFMRhP+kbGKaSG/gZM6RCvwhEbwmCNF06CdDiNaoHcbeg0PqkEa2GOcn8KJ0nnc7+yL7EjoYLHQ==}
+ engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.51.1
+ babel-plugin-react-compiler: '*'
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
+
+ node-exports-info@1.6.0:
+ resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==}
+ engines: {node: '>= 0.4'}
+
+ node-fetch-native@1.6.7:
+ resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
+
+ node-releases@2.0.36:
+ resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==}
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ nwsapi@2.2.23:
+ resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
+
+ nypm@0.6.5:
+ resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ object.assign@4.1.7:
+ resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+ engines: {node: '>= 0.4'}
+
+ object.entries@1.1.9:
+ resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
+ engines: {node: '>= 0.4'}
+
+ object.fromentries@2.0.8:
+ resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+ engines: {node: '>= 0.4'}
+
+ object.groupby@1.0.3:
+ resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
+ engines: {node: '>= 0.4'}
+
+ object.values@1.2.1:
+ resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
+ engines: {node: '>= 0.4'}
+
+ ohash@2.0.11:
+ resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ own-keys@1.0.1:
+ resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+ engines: {node: '>= 0.4'}
+
+ ox@0.14.0:
+ resolution: {integrity: sha512-WLOB7IKnmI3Ol6RAqY7CJdZKl8QaI44LN91OGF1061YIeN6bL5IsFcdp7+oQShRyamE/8fW/CBRWhJAOzI35Dw==}
+ peerDependencies:
+ typescript: '>=5.4.0'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@4.1.0:
+ resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+ engines: {node: '>=8'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parent-module@2.0.0:
+ resolution: {integrity: sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==}
+ engines: {node: '>=8'}
+
+ parse5@7.3.0:
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ pathval@2.0.1:
+ resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
+ engines: {node: '>= 14.16'}
+
+ perfect-debounce@1.0.0:
+ resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ pify@2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
+ pkg-types@2.3.0:
+ resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
+
+ pngjs@5.0.0:
+ resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
+ engines: {node: '>=10.13.0'}
+
+ possible-typed-array-names@1.1.0:
+ resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+ engines: {node: '>= 0.4'}
+
+ postcss-import@15.1.0:
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+
+ postcss-js@4.1.0:
+ resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+
+ postcss-load-config@6.0.1:
+ resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ jiti: '>=1.21.0'
+ postcss: '>=8.0.9'
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+ postcss:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ postcss-nested@6.2.0:
+ resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+
+ postcss-selector-parser@6.1.2:
+ resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+ engines: {node: '>=4'}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postcss@8.5.8:
+ resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier@3.8.1:
+ resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ prisma@6.19.2:
+ resolution: {integrity: sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==}
+ engines: {node: '>=18.18'}
+ hasBin: true
+ peerDependencies:
+ typescript: '>=5.1.0'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ pure-rand@6.1.0:
+ resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
+
+ qrcode@1.5.4:
+ resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ rc9@2.1.2:
+ resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
+
+ react-dom@19.2.4:
+ resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
+ peerDependencies:
+ react: ^19.2.4
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react@19.2.4:
+ resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
+ engines: {node: '>=0.10.0'}
+
+ read-cache@1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+
+ readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+
+ readdirp@4.1.2:
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
+
+ redent@3.0.0:
+ resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
+ engines: {node: '>=8'}
+
+ reflect.getprototypeof@1.0.10:
+ resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+ engines: {node: '>= 0.4'}
+
+ regexp.prototype.flags@1.5.4:
+ resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+ engines: {node: '>= 0.4'}
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ require-main-filename@2.0.0:
+ resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ resolve@1.22.11:
+ resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ resolve@2.0.0-next.6:
+ resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rollup@4.59.0:
+ resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ rrweb-cssom@0.8.0:
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ safe-array-concat@1.1.3:
+ resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
+ engines: {node: '>=0.4'}
+
+ safe-push-apply@1.0.0:
+ resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex-test@1.1.0:
+ resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+ engines: {node: '>= 0.4'}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.4:
+ resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ set-blocking@2.0.0:
+ resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+
+ set-proto@1.0.0:
+ resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+ engines: {node: '>= 0.4'}
+
+ sharp@0.34.5:
+ resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ side-channel-list@1.0.0:
+ resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.0:
+ resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+ engines: {node: '>= 0.4'}
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ siwe@2.3.2:
+ resolution: {integrity: sha512-aSf+6+Latyttbj5nMu6GF3doMfv2UYj83hhwZgUF20ky6fTS83uVhkQABdIVnEuS8y1bBdk7p6ltb9SmlhTTlA==}
+ peerDependencies:
+ ethers: ^5.6.8 || ^6.0.8
+
+ smol-toml@1.6.0:
+ resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==}
+ engines: {node: '>= 18'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
+ stable-hash@0.0.5:
+ resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ std-env@3.10.0:
+ resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+ engines: {node: '>= 0.4'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string.prototype.includes@2.0.1:
+ resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.matchall@4.0.12:
+ resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.repeat@1.0.0:
+ resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
+ string.prototype.trim@1.2.10:
+ resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimend@1.0.9:
+ resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimstart@1.0.8:
+ resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+ engines: {node: '>= 0.4'}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+
+ strip-indent@3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ strip-literal@3.1.0:
+ resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
+
+ styled-jsx@5.1.6:
+ resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
+ sucrase@3.35.1:
+ resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
+ tailwindcss@3.4.19:
+ resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+
+ thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinyexec@1.0.2:
+ resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
+ engines: {node: '>=18'}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ tinypool@1.1.1:
+ resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+
+ tinyrainbow@2.0.0:
+ resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
+ engines: {node: '>=14.0.0'}
+
+ tinyspy@4.0.4:
+ resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
+ engines: {node: '>=14.0.0'}
+
+ tldts-core@6.1.86:
+ resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
+
+ tldts@6.1.86:
+ resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+ hasBin: true
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ tough-cookie@5.1.2:
+ resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+ engines: {node: '>=16'}
+
+ tr46@5.1.1:
+ resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+ engines: {node: '>=18'}
+
+ ts-api-utils@2.4.0:
+ resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
+ ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
+ ts-node@10.9.2:
+ resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': '>=1.2.50'
+ '@swc/wasm': '>=1.2.50'
+ '@types/node': '*'
+ typescript: '>=2.7'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ '@swc/wasm':
+ optional: true
+
+ tsconfig-paths@3.15.0:
+ resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+
+ tslib@2.7.0:
+ resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tweetnacl@1.0.3:
+ resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ typed-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-length@1.0.3:
+ resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-offset@1.0.4:
+ resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-length@1.0.7:
+ resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
+ engines: {node: '>= 0.4'}
+
+ typescript-eslint@8.57.0:
+ resolution: {integrity: sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ uglify-js@3.19.3:
+ resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
+ engines: {node: '>=0.8.0'}
+ hasBin: true
+
+ unbox-primitive@1.1.0:
+ resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+ engines: {node: '>= 0.4'}
+
+ undici-types@6.19.8:
+ resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+
+ undici-types@7.16.0:
+ resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
+ unrs-resolver@1.11.1:
+ resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ v8-compile-cache-lib@3.0.1:
+ resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+
+ valid-url@1.0.9:
+ resolution: {integrity: sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==}
+
+ viem@2.47.1:
+ resolution: {integrity: sha512-frlK109+X5z2vlZeIGKa6Rxev6CcIpumV/VVhaIPc/QFotiB6t/CgUwkMlYfr4F2YNBZZ2l6jguWz2sY1XrQHw==}
+ peerDependencies:
+ typescript: '>=5.0.4'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ vite-node@3.2.4:
+ resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+
+ vite@6.4.1:
+ resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vitest@3.2.4:
+ resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/debug': ^4.1.12
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ '@vitest/browser': 3.2.4
+ '@vitest/ui': 3.2.4
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/debug':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ vscode-languageserver-textdocument@1.0.12:
+ resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==}
+
+ vscode-uri@3.1.0:
+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
+
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
+ webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
+ whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
+
+ whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+
+ whatwg-url@14.2.0:
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+ engines: {node: '>=18'}
+
+ which-boxed-primitive@1.1.1:
+ resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+ engines: {node: '>= 0.4'}
+
+ which-builtin-type@1.2.1:
+ resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+ engines: {node: '>= 0.4'}
+
+ which-collection@1.0.2:
+ resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+ engines: {node: '>= 0.4'}
+
+ which-module@2.0.1:
+ resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
+
+ which-typed-array@1.1.20:
+ resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
+ engines: {node: '>= 0.4'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ wordwrap@1.0.0:
+ resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
+
+ wrap-ansi@6.2.0:
+ resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
+ engines: {node: '>=8'}
+
+ ws@8.17.1:
+ resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ ws@8.18.3:
+ resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ ws@8.19.0:
+ resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xdg-basedir@5.1.0:
+ resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==}
+ engines: {node: '>=12'}
+
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
+ y18n@4.0.3:
+ resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
+
+ yaml@2.8.2:
+ resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
+ yargs-parser@18.1.3:
+ resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
+ engines: {node: '>=6'}
+
+ yargs@15.4.1:
+ resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
+ engines: {node: '>=8'}
+
+ yn@3.1.1:
+ resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+ engines: {node: '>=6'}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ zod@3.25.76:
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+
+snapshots:
+
+ '@adobe/css-tools@4.4.4': {}
+
+ '@adraffy/ens-normalize@1.10.1': {}
+
+ '@adraffy/ens-normalize@1.11.1': {}
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@asamuzakjp/css-color@3.2.0':
+ dependencies:
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ lru-cache: 10.4.3
+
+ '@boundaries/elements@1.2.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))':
+ dependencies:
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@1.21.7))
+ handlebars: 4.7.8
+ is-core-module: 2.16.1
+ micromatch: 4.0.8
+ transitivePeerDependencies:
+ - '@typescript-eslint/parser'
+ - eslint
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+
+ '@cspell/cspell-bundled-dicts@9.7.0':
+ dependencies:
+ '@cspell/dict-ada': 4.1.1
+ '@cspell/dict-al': 1.1.1
+ '@cspell/dict-aws': 4.0.17
+ '@cspell/dict-bash': 4.2.2
+ '@cspell/dict-companies': 3.2.11
+ '@cspell/dict-cpp': 7.0.2
+ '@cspell/dict-cryptocurrencies': 5.0.5
+ '@cspell/dict-csharp': 4.0.8
+ '@cspell/dict-css': 4.1.1
+ '@cspell/dict-dart': 2.3.2
+ '@cspell/dict-data-science': 2.0.13
+ '@cspell/dict-django': 4.1.6
+ '@cspell/dict-docker': 1.1.17
+ '@cspell/dict-dotnet': 5.0.12
+ '@cspell/dict-elixir': 4.0.8
+ '@cspell/dict-en-common-misspellings': 2.1.12
+ '@cspell/dict-en-gb-mit': 3.1.20
+ '@cspell/dict-en_us': 4.4.31
+ '@cspell/dict-filetypes': 3.0.17
+ '@cspell/dict-flutter': 1.1.1
+ '@cspell/dict-fonts': 4.0.6
+ '@cspell/dict-fsharp': 1.1.1
+ '@cspell/dict-fullstack': 3.2.8
+ '@cspell/dict-gaming-terms': 1.1.2
+ '@cspell/dict-git': 3.1.0
+ '@cspell/dict-golang': 6.0.26
+ '@cspell/dict-google': 1.0.9
+ '@cspell/dict-haskell': 4.0.6
+ '@cspell/dict-html': 4.0.15
+ '@cspell/dict-html-symbol-entities': 4.0.5
+ '@cspell/dict-java': 5.0.12
+ '@cspell/dict-julia': 1.1.1
+ '@cspell/dict-k8s': 1.0.12
+ '@cspell/dict-kotlin': 1.1.1
+ '@cspell/dict-latex': 5.1.0
+ '@cspell/dict-lorem-ipsum': 4.0.5
+ '@cspell/dict-lua': 4.0.8
+ '@cspell/dict-makefile': 1.0.5
+ '@cspell/dict-markdown': 2.0.16(@cspell/dict-css@4.1.1)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.15)(@cspell/dict-typescript@3.2.3)
+ '@cspell/dict-monkeyc': 1.0.12
+ '@cspell/dict-node': 5.0.9
+ '@cspell/dict-npm': 5.2.37
+ '@cspell/dict-php': 4.1.1
+ '@cspell/dict-powershell': 5.0.15
+ '@cspell/dict-public-licenses': 2.0.16
+ '@cspell/dict-python': 4.2.25
+ '@cspell/dict-r': 2.1.1
+ '@cspell/dict-ruby': 5.1.1
+ '@cspell/dict-rust': 4.1.2
+ '@cspell/dict-scala': 5.0.9
+ '@cspell/dict-shell': 1.1.2
+ '@cspell/dict-software-terms': 5.2.0
+ '@cspell/dict-sql': 2.2.1
+ '@cspell/dict-svelte': 1.0.7
+ '@cspell/dict-swift': 2.0.6
+ '@cspell/dict-terraform': 1.1.3
+ '@cspell/dict-typescript': 3.2.3
+ '@cspell/dict-vue': 3.0.5
+ '@cspell/dict-zig': 1.0.0
+
+ '@cspell/cspell-json-reporter@9.7.0':
+ dependencies:
+ '@cspell/cspell-types': 9.7.0
+
+ '@cspell/cspell-performance-monitor@9.7.0': {}
+
+ '@cspell/cspell-pipe@9.7.0': {}
+
+ '@cspell/cspell-resolver@9.7.0':
+ dependencies:
+ global-directory: 5.0.0
+
+ '@cspell/cspell-service-bus@9.7.0': {}
+
+ '@cspell/cspell-types@9.7.0': {}
+
+ '@cspell/cspell-worker@9.7.0':
+ dependencies:
+ cspell-lib: 9.7.0
+
+ '@cspell/dict-ada@4.1.1': {}
+
+ '@cspell/dict-al@1.1.1': {}
+
+ '@cspell/dict-aws@4.0.17': {}
+
+ '@cspell/dict-bash@4.2.2':
+ dependencies:
+ '@cspell/dict-shell': 1.1.2
+
+ '@cspell/dict-companies@3.2.11': {}
+
+ '@cspell/dict-cpp@7.0.2': {}
+
+ '@cspell/dict-cryptocurrencies@5.0.5': {}
+
+ '@cspell/dict-csharp@4.0.8': {}
+
+ '@cspell/dict-css@4.1.1': {}
+
+ '@cspell/dict-dart@2.3.2': {}
+
+ '@cspell/dict-data-science@2.0.13': {}
+
+ '@cspell/dict-django@4.1.6': {}
+
+ '@cspell/dict-docker@1.1.17': {}
+
+ '@cspell/dict-dotnet@5.0.12': {}
+
+ '@cspell/dict-elixir@4.0.8': {}
+
+ '@cspell/dict-en-common-misspellings@2.1.12': {}
+
+ '@cspell/dict-en-gb-mit@3.1.20': {}
+
+ '@cspell/dict-en_us@4.4.31': {}
+
+ '@cspell/dict-filetypes@3.0.17': {}
+
+ '@cspell/dict-flutter@1.1.1': {}
+
+ '@cspell/dict-fonts@4.0.6': {}
+
+ '@cspell/dict-fsharp@1.1.1': {}
+
+ '@cspell/dict-fullstack@3.2.8': {}
+
+ '@cspell/dict-gaming-terms@1.1.2': {}
+
+ '@cspell/dict-git@3.1.0': {}
+
+ '@cspell/dict-golang@6.0.26': {}
+
+ '@cspell/dict-google@1.0.9': {}
+
+ '@cspell/dict-haskell@4.0.6': {}
+
+ '@cspell/dict-html-symbol-entities@4.0.5': {}
+
+ '@cspell/dict-html@4.0.15': {}
+
+ '@cspell/dict-java@5.0.12': {}
+
+ '@cspell/dict-julia@1.1.1': {}
+
+ '@cspell/dict-k8s@1.0.12': {}
+
+ '@cspell/dict-kotlin@1.1.1': {}
+
+ '@cspell/dict-latex@5.1.0': {}
+
+ '@cspell/dict-lorem-ipsum@4.0.5': {}
+
+ '@cspell/dict-lua@4.0.8': {}
+
+ '@cspell/dict-makefile@1.0.5': {}
+
+ '@cspell/dict-markdown@2.0.16(@cspell/dict-css@4.1.1)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.15)(@cspell/dict-typescript@3.2.3)':
+ dependencies:
+ '@cspell/dict-css': 4.1.1
+ '@cspell/dict-html': 4.0.15
+ '@cspell/dict-html-symbol-entities': 4.0.5
+ '@cspell/dict-typescript': 3.2.3
+
+ '@cspell/dict-monkeyc@1.0.12': {}
+
+ '@cspell/dict-node@5.0.9': {}
+
+ '@cspell/dict-npm@5.2.37': {}
+
+ '@cspell/dict-php@4.1.1': {}
+
+ '@cspell/dict-powershell@5.0.15': {}
+
+ '@cspell/dict-public-licenses@2.0.16': {}
+
+ '@cspell/dict-python@4.2.25':
+ dependencies:
+ '@cspell/dict-data-science': 2.0.13
+
+ '@cspell/dict-r@2.1.1': {}
+
+ '@cspell/dict-ruby@5.1.1': {}
+
+ '@cspell/dict-rust@4.1.2': {}
+
+ '@cspell/dict-scala@5.0.9': {}
+
+ '@cspell/dict-shell@1.1.2': {}
+
+ '@cspell/dict-software-terms@5.2.0': {}
+
+ '@cspell/dict-sql@2.2.1': {}
+
+ '@cspell/dict-svelte@1.0.7': {}
+
+ '@cspell/dict-swift@2.0.6': {}
+
+ '@cspell/dict-terraform@1.1.3': {}
+
+ '@cspell/dict-typescript@3.2.3': {}
+
+ '@cspell/dict-vue@3.0.5': {}
+
+ '@cspell/dict-zig@1.0.0': {}
+
+ '@cspell/dynamic-import@9.7.0':
+ dependencies:
+ '@cspell/url': 9.7.0
+ import-meta-resolve: 4.2.0
+
+ '@cspell/filetypes@9.7.0': {}
+
+ '@cspell/rpc@9.7.0': {}
+
+ '@cspell/strong-weak-map@9.7.0': {}
+
+ '@cspell/url@9.7.0': {}
+
+ '@cspotcode/source-map-support@0.8.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.9
+
+ '@csstools/color-helpers@5.1.0': {}
+
+ '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/color-helpers': 5.1.0
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-tokenizer@3.0.4': {}
+
+ '@emnapi/core@1.8.1':
+ dependencies:
+ '@emnapi/wasi-threads': 1.1.0
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.8.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.1.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
+ '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@1.21.7))':
+ dependencies:
+ eslint: 9.39.4(jiti@1.21.7)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.2': {}
+
+ '@eslint/config-array@0.21.2':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.3
+ minimatch: 3.1.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.4.2':
+ dependencies:
+ '@eslint/core': 0.17.0
+
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.5':
+ dependencies:
+ ajv: 6.14.0
+ debug: 4.4.3
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.1
+ minimatch: 3.1.5
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.39.4': {}
+
+ '@eslint/object-schema@2.1.7': {}
+
+ '@eslint/plugin-kit@0.4.1':
+ dependencies:
+ '@eslint/core': 0.17.0
+ levn: 0.4.1
+
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.7':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@img/colour@1.1.0':
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-wasm32@0.34.5':
+ dependencies:
+ '@emnapi/runtime': 1.8.1
+ optional: true
+
+ '@img/sharp-win32-arm64@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.34.5':
+ optional: true
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@jridgewell/trace-mapping@0.3.9':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@napi-rs/wasm-runtime@0.2.12':
+ dependencies:
+ '@emnapi/core': 1.8.1
+ '@emnapi/runtime': 1.8.1
+ '@tybys/wasm-util': 0.10.1
+ optional: true
+
+ '@next/env@15.4.10': {}
+
+ '@next/eslint-plugin-next@15.4.1':
+ dependencies:
+ fast-glob: 3.3.1
+
+ '@next/eslint-plugin-next@15.5.12':
+ dependencies:
+ fast-glob: 3.3.1
+
+ '@next/swc-darwin-arm64@15.4.8':
+ optional: true
+
+ '@next/swc-darwin-x64@15.4.8':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@15.4.8':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@15.4.8':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@15.4.8':
+ optional: true
+
+ '@next/swc-linux-x64-musl@15.4.8':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@15.4.8':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@15.4.8':
+ optional: true
+
+ '@noble/ciphers@1.3.0': {}
+
+ '@noble/curves@1.2.0':
+ dependencies:
+ '@noble/hashes': 1.3.2
+
+ '@noble/curves@1.9.1':
+ dependencies:
+ '@noble/hashes': 1.8.0
+
+ '@noble/hashes@1.3.2': {}
+
+ '@noble/hashes@1.8.0': {}
+
+ '@noble/secp256k1@2.3.0': {}
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.1
+
+ '@nolyfill/is-core-module@1.0.39': {}
+
+ '@opentelemetry/api@1.9.0': {}
+
+ '@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)':
+ optionalDependencies:
+ prisma: 6.19.2(typescript@5.9.3)
+ typescript: 5.9.3
+
+ '@prisma/config@6.19.2':
+ dependencies:
+ c12: 3.1.0
+ deepmerge-ts: 7.1.5
+ effect: 3.18.4
+ empathic: 2.0.0
+ transitivePeerDependencies:
+ - magicast
+
+ '@prisma/debug@6.19.2': {}
+
+ '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': {}
+
+ '@prisma/engines@6.19.2':
+ dependencies:
+ '@prisma/debug': 6.19.2
+ '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7
+ '@prisma/fetch-engine': 6.19.2
+ '@prisma/get-platform': 6.19.2
+
+ '@prisma/fetch-engine@6.19.2':
+ dependencies:
+ '@prisma/debug': 6.19.2
+ '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7
+ '@prisma/get-platform': 6.19.2
+
+ '@prisma/get-platform@6.19.2':
+ dependencies:
+ '@prisma/debug': 6.19.2
+
+ '@rollup/rollup-android-arm-eabi@4.59.0':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.59.0':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.59.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.59.0':
+ optional: true
+
+ '@rtsao/scc@1.1.0': {}
+
+ '@rushstack/eslint-patch@1.16.1': {}
+
+ '@scure/base@1.2.6': {}
+
+ '@scure/bip32@1.7.0':
+ dependencies:
+ '@noble/curves': 1.9.1
+ '@noble/hashes': 1.8.0
+ '@scure/base': 1.2.6
+
+ '@scure/bip39@1.6.0':
+ dependencies:
+ '@noble/hashes': 1.8.0
+ '@scure/base': 1.2.6
+
+ '@spruceid/siwe-parser@2.1.2':
+ dependencies:
+ '@noble/hashes': 1.8.0
+ apg-js: 4.4.0
+ uri-js: 4.4.1
+ valid-url: 1.0.9
+
+ '@stablelib/binary@1.0.1':
+ dependencies:
+ '@stablelib/int': 1.0.1
+
+ '@stablelib/int@1.0.1': {}
+
+ '@stablelib/random@1.0.2':
+ dependencies:
+ '@stablelib/binary': 1.0.1
+ '@stablelib/wipe': 1.0.1
+
+ '@stablelib/wipe@1.0.1': {}
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@supabase/auth-js@2.99.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@supabase/functions-js@2.99.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@supabase/postgrest-js@2.99.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@supabase/realtime-js@2.99.0':
+ dependencies:
+ '@types/phoenix': 1.6.7
+ '@types/ws': 8.18.1
+ tslib: 2.8.1
+ ws: 8.19.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ '@supabase/storage-js@2.99.0':
+ dependencies:
+ iceberg-js: 0.8.1
+ tslib: 2.8.1
+
+ '@supabase/supabase-js@2.99.0':
+ dependencies:
+ '@supabase/auth-js': 2.99.0
+ '@supabase/functions-js': 2.99.0
+ '@supabase/postgrest-js': 2.99.0
+ '@supabase/realtime-js': 2.99.0
+ '@supabase/storage-js': 2.99.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@testing-library/jest-dom@6.9.1':
+ dependencies:
+ '@adobe/css-tools': 4.4.4
+ aria-query: 5.3.2
+ css.escape: 1.5.1
+ dom-accessibility-api: 0.6.3
+ picocolors: 1.1.1
+ redent: 3.0.0
+
+ '@tsconfig/node10@1.0.12': {}
+
+ '@tsconfig/node12@1.0.11': {}
+
+ '@tsconfig/node14@1.0.3': {}
+
+ '@tsconfig/node16@1.0.4': {}
+
+ '@tybys/wasm-util@0.10.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
+ '@types/deep-eql@4.0.2': {}
+
+ '@types/estree@1.0.8': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/json5@0.0.29': {}
+
+ '@types/node@22.7.5':
+ dependencies:
+ undici-types: 6.19.8
+
+ '@types/node@24.12.0':
+ dependencies:
+ undici-types: 7.16.0
+
+ '@types/phoenix@1.6.7': {}
+
+ '@types/react-dom@19.2.3(@types/react@19.2.14)':
+ dependencies:
+ '@types/react': 19.2.14
+
+ '@types/react@19.2.14':
+ dependencies:
+ csstype: 3.2.3
+
+ '@types/ws@8.18.1':
+ dependencies:
+ '@types/node': 24.12.0
+
+ '@typescript-eslint/eslint-plugin@8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.57.0
+ '@typescript-eslint/type-utils': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.57.0
+ eslint: 9.39.4(jiti@1.21.7)
+ ignore: 7.0.5
+ natural-compare: 1.4.0
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.57.0
+ '@typescript-eslint/types': 8.57.0
+ '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.57.0
+ debug: 4.4.3
+ eslint: 9.39.4(jiti@1.21.7)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/project-service@8.57.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.57.0
+ debug: 4.4.3
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.57.0':
+ dependencies:
+ '@typescript-eslint/types': 8.57.0
+ '@typescript-eslint/visitor-keys': 8.57.0
+
+ '@typescript-eslint/tsconfig-utils@8.57.0(typescript@5.9.3)':
+ dependencies:
+ typescript: 5.9.3
+
+ '@typescript-eslint/type-utils@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/types': 8.57.0
+ '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ debug: 4.4.3
+ eslint: 9.39.4(jiti@1.21.7)
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@8.57.0': {}
+
+ '@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.57.0(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.57.0
+ '@typescript-eslint/visitor-keys': 8.57.0
+ debug: 4.4.3
+ minimatch: 10.2.4
+ semver: 7.7.4
+ tinyglobby: 0.2.15
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
+ '@typescript-eslint/scope-manager': 8.57.0
+ '@typescript-eslint/types': 8.57.0
+ '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3)
+ eslint: 9.39.4(jiti@1.21.7)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.57.0':
+ dependencies:
+ '@typescript-eslint/types': 8.57.0
+ eslint-visitor-keys: 5.0.1
+
+ '@unrs/resolver-binding-android-arm-eabi@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-android-arm64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-arm64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-x64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-freebsd-x64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-wasm32-wasi@1.11.1':
+ dependencies:
+ '@napi-rs/wasm-runtime': 0.2.12
+ optional: true
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
+ optional: true
+
+ '@vercel/analytics@1.6.1(next@15.4.10(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)':
+ optionalDependencies:
+ next: 15.4.10(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+
+ '@vitest/expect@3.2.4':
+ dependencies:
+ '@types/chai': 5.2.3
+ '@vitest/spy': 3.2.4
+ '@vitest/utils': 3.2.4
+ chai: 5.3.3
+ tinyrainbow: 2.0.0
+
+ '@vitest/mocker@3.2.4(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.2))':
+ dependencies:
+ '@vitest/spy': 3.2.4
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.2)
+
+ '@vitest/pretty-format@3.2.4':
+ dependencies:
+ tinyrainbow: 2.0.0
+
+ '@vitest/runner@3.2.4':
+ dependencies:
+ '@vitest/utils': 3.2.4
+ pathe: 2.0.3
+ strip-literal: 3.1.0
+
+ '@vitest/snapshot@3.2.4':
+ dependencies:
+ '@vitest/pretty-format': 3.2.4
+ magic-string: 0.30.21
+ pathe: 2.0.3
+
+ '@vitest/spy@3.2.4':
+ dependencies:
+ tinyspy: 4.0.4
+
+ '@vitest/utils@3.2.4':
+ dependencies:
+ '@vitest/pretty-format': 3.2.4
+ loupe: 3.2.1
+ tinyrainbow: 2.0.0
+
+ '@worldcoin/agentkit@0.0.3(ethers@6.16.0)(typescript@5.9.3)':
+ dependencies:
+ '@scure/base': 1.2.6
+ '@x402/core': 2.6.0
+ siwe: 2.3.2(ethers@6.16.0)
+ tweetnacl: 1.0.3
+ viem: 2.47.1(typescript@5.9.3)(zod@3.25.76)
+ zod: 3.25.76
+ transitivePeerDependencies:
+ - bufferutil
+ - ethers
+ - typescript
+ - utf-8-validate
+
+ '@worldcoin/idkit-core@4.0.11':
+ dependencies:
+ '@noble/hashes': 1.8.0
+ '@worldcoin/idkit-server': 1.0.0
+
+ '@worldcoin/idkit-server@1.0.0':
+ dependencies:
+ '@noble/hashes': 1.8.0
+ '@noble/secp256k1': 2.3.0
+
+ '@worldcoin/idkit@4.0.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@worldcoin/idkit-core': 4.0.11
+ qrcode: 1.5.4
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+
+ '@x402/core@2.6.0':
+ dependencies:
+ zod: 3.25.76
+
+ abitype@1.2.3(typescript@5.9.3)(zod@3.25.76):
+ optionalDependencies:
+ typescript: 5.9.3
+ zod: 3.25.76
+
+ acorn-jsx@5.3.2(acorn@8.16.0):
+ dependencies:
+ acorn: 8.16.0
+
+ acorn-walk@8.3.5:
+ dependencies:
+ acorn: 8.16.0
+
+ acorn@8.16.0: {}
+
+ aes-js@4.0.0-beta.5: {}
+
+ agent-base@7.1.4: {}
+
+ ajv@6.14.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.2.2: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ any-promise@1.3.0: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
+ apg-js@4.4.0: {}
+
+ arg@4.1.3: {}
+
+ arg@5.0.2: {}
+
+ argparse@2.0.1: {}
+
+ aria-query@5.3.2: {}
+
+ array-buffer-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ is-array-buffer: 3.0.5
+
+ array-includes@3.1.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ is-string: 1.1.1
+ math-intrinsics: 1.1.0
+
+ array-timsort@1.0.3: {}
+
+ array.prototype.findlast@1.2.5:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.findlastindex@1.2.6:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flat@1.3.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flatmap@1.3.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.tosorted@1.1.4:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.1.0
+
+ arraybuffer.prototype.slice@1.0.4:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ is-array-buffer: 3.0.5
+
+ assertion-error@2.0.1: {}
+
+ ast-types-flow@0.0.8: {}
+
+ async-function@1.0.0: {}
+
+ autoprefixer@10.4.27(postcss@8.5.8):
+ dependencies:
+ browserslist: 4.28.1
+ caniuse-lite: 1.0.30001777
+ fraction.js: 5.3.4
+ picocolors: 1.1.1
+ postcss: 8.5.8
+ postcss-value-parser: 4.2.0
+
+ available-typed-arrays@1.0.7:
+ dependencies:
+ possible-typed-array-names: 1.1.0
+
+ axe-core@4.11.1: {}
+
+ axobject-query@4.1.0: {}
+
+ balanced-match@1.0.2: {}
+
+ balanced-match@4.0.4: {}
+
+ baseline-browser-mapping@2.10.0: {}
+
+ binary-extensions@2.3.0: {}
+
+ brace-expansion@1.1.12:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@5.0.4:
+ dependencies:
+ balanced-match: 4.0.4
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.10.0
+ caniuse-lite: 1.0.30001777
+ electron-to-chromium: 1.5.307
+ node-releases: 2.0.36
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ c12@3.1.0:
+ dependencies:
+ chokidar: 4.0.3
+ confbox: 0.2.4
+ defu: 6.1.4
+ dotenv: 16.6.1
+ exsolve: 1.0.8
+ giget: 2.0.0
+ jiti: 2.6.1
+ ohash: 2.0.11
+ pathe: 2.0.3
+ perfect-debounce: 1.0.0
+ pkg-types: 2.3.0
+ rc9: 2.1.2
+
+ cac@6.7.14: {}
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bind@1.0.8:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ get-intrinsic: 1.3.0
+ set-function-length: 1.2.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ callsites@3.1.0: {}
+
+ camelcase-css@2.0.1: {}
+
+ camelcase@5.3.1: {}
+
+ caniuse-lite@1.0.30001777: {}
+
+ chai@5.3.3:
+ dependencies:
+ assertion-error: 2.0.1
+ check-error: 2.1.3
+ deep-eql: 5.0.2
+ loupe: 3.2.1
+ pathval: 2.0.1
+
+ chalk-template@1.1.2:
+ dependencies:
+ chalk: 5.6.2
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chalk@5.6.2: {}
+
+ check-error@2.1.3: {}
+
+ chokidar@3.6.0:
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.3
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ chokidar@4.0.3:
+ dependencies:
+ readdirp: 4.1.2
+
+ citty@0.1.6:
+ dependencies:
+ consola: 3.4.2
+
+ citty@0.2.1: {}
+
+ clear-module@4.1.2:
+ dependencies:
+ parent-module: 2.0.0
+ resolve-from: 5.0.0
+
+ client-only@0.0.1: {}
+
+ cliui@6.0.0:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 6.2.0
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ commander@14.0.3: {}
+
+ commander@4.1.1: {}
+
+ comment-json@4.6.2:
+ dependencies:
+ array-timsort: 1.0.3
+ esprima: 4.0.1
+
+ concat-map@0.0.1: {}
+
+ confbox@0.2.4: {}
+
+ consola@3.4.2: {}
+
+ create-require@1.1.1: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ cspell-config-lib@9.7.0:
+ dependencies:
+ '@cspell/cspell-types': 9.7.0
+ comment-json: 4.6.2
+ smol-toml: 1.6.0
+ yaml: 2.8.2
+
+ cspell-dictionary@9.7.0:
+ dependencies:
+ '@cspell/cspell-performance-monitor': 9.7.0
+ '@cspell/cspell-pipe': 9.7.0
+ '@cspell/cspell-types': 9.7.0
+ cspell-trie-lib: 9.7.0(@cspell/cspell-types@9.7.0)
+ fast-equals: 6.0.0
+
+ cspell-gitignore@9.7.0:
+ dependencies:
+ '@cspell/url': 9.7.0
+ cspell-glob: 9.7.0
+ cspell-io: 9.7.0
+
+ cspell-glob@9.7.0:
+ dependencies:
+ '@cspell/url': 9.7.0
+ picomatch: 4.0.3
+
+ cspell-grammar@9.7.0:
+ dependencies:
+ '@cspell/cspell-pipe': 9.7.0
+ '@cspell/cspell-types': 9.7.0
+
+ cspell-io@9.7.0:
+ dependencies:
+ '@cspell/cspell-service-bus': 9.7.0
+ '@cspell/url': 9.7.0
+
+ cspell-lib@9.7.0:
+ dependencies:
+ '@cspell/cspell-bundled-dicts': 9.7.0
+ '@cspell/cspell-performance-monitor': 9.7.0
+ '@cspell/cspell-pipe': 9.7.0
+ '@cspell/cspell-resolver': 9.7.0
+ '@cspell/cspell-types': 9.7.0
+ '@cspell/dynamic-import': 9.7.0
+ '@cspell/filetypes': 9.7.0
+ '@cspell/rpc': 9.7.0
+ '@cspell/strong-weak-map': 9.7.0
+ '@cspell/url': 9.7.0
+ clear-module: 4.1.2
+ cspell-config-lib: 9.7.0
+ cspell-dictionary: 9.7.0
+ cspell-glob: 9.7.0
+ cspell-grammar: 9.7.0
+ cspell-io: 9.7.0
+ cspell-trie-lib: 9.7.0(@cspell/cspell-types@9.7.0)
+ env-paths: 4.0.0
+ gensequence: 8.0.8
+ import-fresh: 3.3.1
+ resolve-from: 5.0.0
+ vscode-languageserver-textdocument: 1.0.12
+ vscode-uri: 3.1.0
+ xdg-basedir: 5.1.0
+
+ cspell-trie-lib@9.7.0(@cspell/cspell-types@9.7.0):
+ dependencies:
+ '@cspell/cspell-types': 9.7.0
+
+ cspell@9.7.0:
+ dependencies:
+ '@cspell/cspell-json-reporter': 9.7.0
+ '@cspell/cspell-performance-monitor': 9.7.0
+ '@cspell/cspell-pipe': 9.7.0
+ '@cspell/cspell-types': 9.7.0
+ '@cspell/cspell-worker': 9.7.0
+ '@cspell/dynamic-import': 9.7.0
+ '@cspell/url': 9.7.0
+ ansi-regex: 6.2.2
+ chalk: 5.6.2
+ chalk-template: 1.1.2
+ commander: 14.0.3
+ cspell-config-lib: 9.7.0
+ cspell-dictionary: 9.7.0
+ cspell-gitignore: 9.7.0
+ cspell-glob: 9.7.0
+ cspell-io: 9.7.0
+ cspell-lib: 9.7.0
+ fast-json-stable-stringify: 2.1.0
+ flatted: 3.4.1
+ semver: 7.7.4
+ tinyglobby: 0.2.15
+
+ css.escape@1.5.1: {}
+
+ cssesc@3.0.0: {}
+
+ cssstyle@4.6.0:
+ dependencies:
+ '@asamuzakjp/css-color': 3.2.0
+ rrweb-cssom: 0.8.0
+
+ csstype@3.2.3: {}
+
+ damerau-levenshtein@1.0.8: {}
+
+ data-urls@5.0.0:
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+
+ data-view-buffer@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-offset@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ debug@3.2.7:
+ dependencies:
+ ms: 2.1.3
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decamelize@1.2.0: {}
+
+ decimal.js@10.6.0: {}
+
+ deep-eql@5.0.2: {}
+
+ deep-is@0.1.4: {}
+
+ deepmerge-ts@7.1.5: {}
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ defu@6.1.4: {}
+
+ destr@2.0.5: {}
+
+ detect-libc@2.1.2:
+ optional: true
+
+ didyoumean@1.2.2: {}
+
+ diff@4.0.4: {}
+
+ dijkstrajs@1.0.3: {}
+
+ dlv@1.1.3: {}
+
+ doctrine@2.1.0:
+ dependencies:
+ esutils: 2.0.3
+
+ dom-accessibility-api@0.6.3: {}
+
+ dotenv@16.6.1: {}
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ effect@3.18.4:
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ fast-check: 3.23.2
+
+ electron-to-chromium@1.5.307: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ empathic@2.0.0: {}
+
+ entities@6.0.1: {}
+
+ env-paths@4.0.0:
+ dependencies:
+ is-safe-filename: 0.1.1
+
+ es-abstract@1.24.1:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ arraybuffer.prototype.slice: 1.0.4
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ data-view-buffer: 1.0.2
+ data-view-byte-length: 1.0.2
+ data-view-byte-offset: 1.0.1
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-set-tostringtag: 2.1.0
+ es-to-primitive: 1.3.0
+ function.prototype.name: 1.1.8
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ get-symbol-description: 1.1.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ internal-slot: 1.1.0
+ is-array-buffer: 3.0.5
+ is-callable: 1.2.7
+ is-data-view: 1.0.2
+ is-negative-zero: 2.0.3
+ is-regex: 1.2.1
+ is-set: 2.0.3
+ is-shared-array-buffer: 1.0.4
+ is-string: 1.1.1
+ is-typed-array: 1.1.15
+ is-weakref: 1.1.1
+ math-intrinsics: 1.1.0
+ object-inspect: 1.13.4
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ own-keys: 1.0.1
+ regexp.prototype.flags: 1.5.4
+ safe-array-concat: 1.1.3
+ safe-push-apply: 1.0.0
+ safe-regex-test: 1.1.0
+ set-proto: 1.0.0
+ stop-iteration-iterator: 1.1.0
+ string.prototype.trim: 1.2.10
+ string.prototype.trimend: 1.0.9
+ string.prototype.trimstart: 1.0.8
+ typed-array-buffer: 1.0.3
+ typed-array-byte-length: 1.0.3
+ typed-array-byte-offset: 1.0.4
+ typed-array-length: 1.0.7
+ unbox-primitive: 1.1.0
+ which-typed-array: 1.1.20
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-iterator-helpers@1.2.2:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-set-tostringtag: 2.1.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ iterator.prototype: 1.1.5
+ safe-array-concat: 1.1.3
+
+ es-module-lexer@1.7.0: {}
+
+ es-object-atoms@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ es-shim-unscopables@1.1.0:
+ dependencies:
+ hasown: 2.0.2
+
+ es-to-primitive@1.3.0:
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.1.0
+ is-symbol: 1.1.1
+
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-config-next@15.4.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3):
+ dependencies:
+ '@next/eslint-plugin-next': 15.4.1
+ '@rushstack/eslint-patch': 1.16.1
+ '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ eslint: 9.39.4(jiti@1.21.7)
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@1.21.7))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@1.21.7))
+ eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@1.21.7))
+ eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@1.21.7))
+ eslint-plugin-react-hooks: 5.2.0(eslint@9.39.4(jiti@1.21.7))
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - eslint-import-resolver-webpack
+ - eslint-plugin-import-x
+ - supports-color
+
+ eslint-import-resolver-node@0.3.9:
+ dependencies:
+ debug: 3.2.7
+ is-core-module: 2.16.1
+ resolve: 1.22.11
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@1.21.7)):
+ dependencies:
+ '@nolyfill/is-core-module': 1.0.39
+ debug: 4.4.3
+ eslint: 9.39.4(jiti@1.21.7)
+ get-tsconfig: 4.13.6
+ is-bun-module: 2.0.0
+ stable-hash: 0.0.5
+ tinyglobby: 0.2.15
+ unrs-resolver: 1.11.1
+ optionalDependencies:
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@1.21.7))
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@1.21.7)):
+ dependencies:
+ debug: 3.2.7
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ eslint: 9.39.4(jiti@1.21.7)
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@1.21.7))
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-boundaries@5.4.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7)):
+ dependencies:
+ '@boundaries/elements': 1.2.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))
+ chalk: 4.1.2
+ eslint: 9.39.4(jiti@1.21.7)
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@1.21.7))
+ micromatch: 4.0.8
+ transitivePeerDependencies:
+ - '@typescript-eslint/parser'
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@1.21.7)):
+ dependencies:
+ '@rtsao/scc': 1.1.0
+ array-includes: 3.1.9
+ array.prototype.findlastindex: 1.2.6
+ array.prototype.flat: 1.3.3
+ array.prototype.flatmap: 1.3.3
+ debug: 3.2.7
+ doctrine: 2.1.0
+ eslint: 9.39.4(jiti@1.21.7)
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@1.21.7))
+ hasown: 2.0.2
+ is-core-module: 2.16.1
+ is-glob: 4.0.3
+ minimatch: 3.1.5
+ object.fromentries: 2.0.8
+ object.groupby: 1.0.3
+ object.values: 1.2.1
+ semver: 6.3.1
+ string.prototype.trimend: 1.0.9
+ tsconfig-paths: 3.15.0
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+
+ eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@1.21.7)):
+ dependencies:
+ aria-query: 5.3.2
+ array-includes: 3.1.9
+ array.prototype.flatmap: 1.3.3
+ ast-types-flow: 0.0.8
+ axe-core: 4.11.1
+ axobject-query: 4.1.0
+ damerau-levenshtein: 1.0.8
+ emoji-regex: 9.2.2
+ eslint: 9.39.4(jiti@1.21.7)
+ hasown: 2.0.2
+ jsx-ast-utils: 3.3.5
+ language-tags: 1.0.9
+ minimatch: 3.1.5
+ object.fromentries: 2.0.8
+ safe-regex-test: 1.1.0
+ string.prototype.includes: 2.0.1
+
+ eslint-plugin-react-hooks@5.2.0(eslint@9.39.4(jiti@1.21.7)):
+ dependencies:
+ eslint: 9.39.4(jiti@1.21.7)
+
+ eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@1.21.7)):
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.findlast: 1.2.5
+ array.prototype.flatmap: 1.3.3
+ array.prototype.tosorted: 1.1.4
+ doctrine: 2.1.0
+ es-iterator-helpers: 1.2.2
+ eslint: 9.39.4(jiti@1.21.7)
+ estraverse: 5.3.0
+ hasown: 2.0.2
+ jsx-ast-utils: 3.3.5
+ minimatch: 3.1.5
+ object.entries: 1.1.9
+ object.fromentries: 2.0.8
+ object.values: 1.2.1
+ prop-types: 15.8.1
+ resolve: 2.0.0-next.6
+ semver: 6.3.1
+ string.prototype.matchall: 4.0.12
+ string.prototype.repeat: 1.0.0
+
+ eslint-plugin-tailwindcss@3.18.2(tailwindcss@3.4.19(yaml@2.8.2)):
+ dependencies:
+ fast-glob: 3.3.3
+ postcss: 8.5.8
+ tailwindcss: 3.4.19(yaml@2.8.2)
+
+ eslint-scope@8.4.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint-visitor-keys@5.0.1: {}
+
+ eslint@9.39.4(jiti@1.21.7):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
+ '@eslint-community/regexpp': 4.12.2
+ '@eslint/config-array': 0.21.2
+ '@eslint/config-helpers': 0.4.2
+ '@eslint/core': 0.17.0
+ '@eslint/eslintrc': 3.3.5
+ '@eslint/js': 9.39.4
+ '@eslint/plugin-kit': 0.4.1
+ '@humanfs/node': 0.16.7
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ ajv: 6.14.0
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.7.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.5
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ optionalDependencies:
+ jiti: 1.21.7
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.16.0
+ acorn-jsx: 5.3.2(acorn@8.16.0)
+ eslint-visitor-keys: 4.2.1
+
+ esprima@4.0.1: {}
+
+ esquery@1.7.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ esutils@2.0.3: {}
+
+ ethers@6.16.0:
+ dependencies:
+ '@adraffy/ens-normalize': 1.10.1
+ '@noble/curves': 1.2.0
+ '@noble/hashes': 1.3.2
+ '@types/node': 22.7.5
+ aes-js: 4.0.0-beta.5
+ tslib: 2.7.0
+ ws: 8.17.1
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ eventemitter3@5.0.1: {}
+
+ expect-type@1.3.0: {}
+
+ exsolve@1.0.8: {}
+
+ fast-check@3.23.2:
+ dependencies:
+ pure-rand: 6.1.0
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-equals@6.0.0: {}
+
+ fast-glob@3.3.1:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fastq@1.20.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-up@4.1.0:
+ dependencies:
+ locate-path: 5.0.0
+ path-exists: 4.0.0
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.4.1
+ keyv: 4.5.4
+
+ flatted@3.4.1: {}
+
+ for-each@0.3.5:
+ dependencies:
+ is-callable: 1.2.7
+
+ fraction.js@5.3.4: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ function.prototype.name@1.1.8:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ functions-have-names: 1.2.3
+ hasown: 2.0.2
+ is-callable: 1.2.7
+
+ functions-have-names@1.2.3: {}
+
+ generator-function@2.0.1: {}
+
+ gensequence@8.0.8: {}
+
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ math-intrinsics: 1.1.0
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
+
+ get-symbol-description@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+
+ get-tsconfig@4.13.6:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ giget@2.0.0:
+ dependencies:
+ citty: 0.1.6
+ consola: 3.4.2
+ defu: 6.1.4
+ node-fetch-native: 1.6.7
+ nypm: 0.6.5
+ pathe: 2.0.3
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ global-directory@5.0.0:
+ dependencies:
+ ini: 6.0.0
+
+ globals@14.0.0: {}
+
+ globalthis@1.0.4:
+ dependencies:
+ define-properties: 1.2.1
+ gopd: 1.2.0
+
+ gopd@1.2.0: {}
+
+ handlebars@4.7.8:
+ dependencies:
+ minimist: 1.2.8
+ neo-async: 2.6.2
+ source-map: 0.6.1
+ wordwrap: 1.0.0
+ optionalDependencies:
+ uglify-js: 3.19.3
+
+ has-bigints@1.1.0: {}
+
+ has-flag@4.0.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ has-proto@1.2.0:
+ dependencies:
+ dunder-proto: 1.0.1
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ html-encoding-sniffer@4.0.0:
+ dependencies:
+ whatwg-encoding: 3.1.1
+
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ iceberg-js@0.8.1: {}
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ ignore@5.3.2: {}
+
+ ignore@7.0.5: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ import-meta-resolve@4.2.0: {}
+
+ imurmurhash@0.1.4: {}
+
+ indent-string@4.0.0: {}
+
+ ini@6.0.0: {}
+
+ internal-slot@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.2
+ side-channel: 1.1.0
+
+ is-array-buffer@3.0.5:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-async-function@2.1.1:
+ dependencies:
+ async-function: 1.0.0
+ call-bound: 1.0.4
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-bigint@1.1.0:
+ dependencies:
+ has-bigints: 1.1.0
+
+ is-binary-path@2.1.0:
+ dependencies:
+ binary-extensions: 2.3.0
+
+ is-boolean-object@1.2.2:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-bun-module@2.0.0:
+ dependencies:
+ semver: 7.7.4
+
+ is-callable@1.2.7: {}
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-data-view@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ is-typed-array: 1.1.15
+
+ is-date-object@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-extglob@2.1.1: {}
+
+ is-finalizationregistry@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-generator-function@1.1.2:
+ dependencies:
+ call-bound: 1.0.4
+ generator-function: 2.0.1
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-map@2.0.3: {}
+
+ is-negative-zero@2.0.3: {}
+
+ is-number-object@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-number@7.0.0: {}
+
+ is-potential-custom-element-name@1.0.1: {}
+
+ is-regex@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ is-safe-filename@0.1.1: {}
+
+ is-set@2.0.3: {}
+
+ is-shared-array-buffer@1.0.4:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-string@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-symbol@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-symbols: 1.1.0
+ safe-regex-test: 1.1.0
+
+ is-typed-array@1.1.15:
+ dependencies:
+ which-typed-array: 1.1.20
+
+ is-weakmap@2.0.2: {}
+
+ is-weakref@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-weakset@2.0.4:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ isarray@2.0.5: {}
+
+ isexe@2.0.0: {}
+
+ isows@1.0.7(ws@8.18.3):
+ dependencies:
+ ws: 8.18.3
+
+ iterator.prototype@1.1.5:
+ dependencies:
+ define-data-property: 1.1.4
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ has-symbols: 1.1.0
+ set-function-name: 2.0.2
+
+ jiti@1.21.7: {}
+
+ jiti@2.6.1: {}
+
+ js-tokens@4.0.0: {}
+
+ js-tokens@9.0.1: {}
+
+ js-yaml@4.1.1:
+ dependencies:
+ argparse: 2.0.1
+
+ jsdom@26.1.0:
+ dependencies:
+ cssstyle: 4.6.0
+ data-urls: 5.0.0
+ decimal.js: 10.6.0
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.23
+ parse5: 7.3.0
+ rrweb-cssom: 0.8.0
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 5.1.2
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+ ws: 8.19.0
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@1.0.2:
+ dependencies:
+ minimist: 1.2.8
+
+ jsx-ast-utils@3.3.5:
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.flat: 1.3.3
+ object.assign: 4.1.7
+ object.values: 1.2.1
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ language-subtag-registry@0.3.23: {}
+
+ language-tags@1.0.9:
+ dependencies:
+ language-subtag-registry: 0.3.23
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ lilconfig@3.1.3: {}
+
+ lines-and-columns@1.2.4: {}
+
+ locate-path@5.0.0:
+ dependencies:
+ p-locate: 4.1.0
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.merge@4.6.2: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ loupe@3.2.1: {}
+
+ lru-cache@10.4.3: {}
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ make-error@1.3.6: {}
+
+ math-intrinsics@1.1.0: {}
+
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ min-indent@1.0.1: {}
+
+ minimatch@10.2.4:
+ dependencies:
+ brace-expansion: 5.0.4
+
+ minimatch@3.1.5:
+ dependencies:
+ brace-expansion: 1.1.12
+
+ minimist@1.2.8: {}
+
+ ms@2.1.3: {}
+
+ mz@2.7.0:
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+
+ nanoid@3.3.11: {}
+
+ napi-postinstall@0.3.4: {}
+
+ natural-compare@1.4.0: {}
+
+ neo-async@2.6.2: {}
+
+ next@15.4.10(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+ dependencies:
+ '@next/env': 15.4.10
+ '@swc/helpers': 0.5.15
+ caniuse-lite: 1.0.30001777
+ postcss: 8.4.31
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ styled-jsx: 5.1.6(react@19.2.4)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 15.4.8
+ '@next/swc-darwin-x64': 15.4.8
+ '@next/swc-linux-arm64-gnu': 15.4.8
+ '@next/swc-linux-arm64-musl': 15.4.8
+ '@next/swc-linux-x64-gnu': 15.4.8
+ '@next/swc-linux-x64-musl': 15.4.8
+ '@next/swc-win32-arm64-msvc': 15.4.8
+ '@next/swc-win32-x64-msvc': 15.4.8
+ '@opentelemetry/api': 1.9.0
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ node-exports-info@1.6.0:
+ dependencies:
+ array.prototype.flatmap: 1.3.3
+ es-errors: 1.3.0
+ object.entries: 1.1.9
+ semver: 6.3.1
+
+ node-fetch-native@1.6.7: {}
+
+ node-releases@2.0.36: {}
+
+ normalize-path@3.0.0: {}
+
+ nwsapi@2.2.23: {}
+
+ nypm@0.6.5:
+ dependencies:
+ citty: 0.2.1
+ pathe: 2.0.3
+ tinyexec: 1.0.2
+
+ object-assign@4.1.1: {}
+
+ object-hash@3.0.0: {}
+
+ object-inspect@1.13.4: {}
+
+ object-keys@1.1.1: {}
+
+ object.assign@4.1.7:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+ has-symbols: 1.1.0
+ object-keys: 1.1.1
+
+ object.entries@1.1.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ object.fromentries@2.0.8:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+
+ object.groupby@1.0.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+
+ object.values@1.2.1:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ ohash@2.0.11: {}
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ own-keys@1.0.1:
+ dependencies:
+ get-intrinsic: 1.3.0
+ object-keys: 1.1.1
+ safe-push-apply: 1.0.0
+
+ ox@0.14.0(typescript@5.9.3)(zod@3.25.76):
+ dependencies:
+ '@adraffy/ens-normalize': 1.11.1
+ '@noble/ciphers': 1.3.0
+ '@noble/curves': 1.9.1
+ '@noble/hashes': 1.8.0
+ '@scure/bip32': 1.7.0
+ '@scure/bip39': 1.6.0
+ abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76)
+ eventemitter3: 5.0.1
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - zod
+
+ p-limit@2.3.0:
+ dependencies:
+ p-try: 2.2.0
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@4.1.0:
+ dependencies:
+ p-limit: 2.3.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ p-try@2.2.0: {}
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ parent-module@2.0.0:
+ dependencies:
+ callsites: 3.1.0
+
+ parse5@7.3.0:
+ dependencies:
+ entities: 6.0.1
+
+ path-exists@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ pathe@2.0.3: {}
+
+ pathval@2.0.1: {}
+
+ perfect-debounce@1.0.0: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.1: {}
+
+ picomatch@4.0.3: {}
+
+ pify@2.3.0: {}
+
+ pirates@4.0.7: {}
+
+ pkg-types@2.3.0:
+ dependencies:
+ confbox: 0.2.4
+ exsolve: 1.0.8
+ pathe: 2.0.3
+
+ pngjs@5.0.0: {}
+
+ possible-typed-array-names@1.1.0: {}
+
+ postcss-import@15.1.0(postcss@8.5.8):
+ dependencies:
+ postcss: 8.5.8
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.11
+
+ postcss-js@4.1.0(postcss@8.5.8):
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.5.8
+
+ postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.8)(yaml@2.8.2):
+ dependencies:
+ lilconfig: 3.1.3
+ optionalDependencies:
+ jiti: 1.21.7
+ postcss: 8.5.8
+ yaml: 2.8.2
+
+ postcss-nested@6.2.0(postcss@8.5.8):
+ dependencies:
+ postcss: 8.5.8
+ postcss-selector-parser: 6.1.2
+
+ postcss-selector-parser@6.1.2:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-value-parser@4.2.0: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postcss@8.5.8:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prelude-ls@1.2.1: {}
+
+ prettier@3.8.1: {}
+
+ prisma@6.19.2(typescript@5.9.3):
+ dependencies:
+ '@prisma/config': 6.19.2
+ '@prisma/engines': 6.19.2
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - magicast
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ punycode@2.3.1: {}
+
+ pure-rand@6.1.0: {}
+
+ qrcode@1.5.4:
+ dependencies:
+ dijkstrajs: 1.0.3
+ pngjs: 5.0.0
+ yargs: 15.4.1
+
+ queue-microtask@1.2.3: {}
+
+ rc9@2.1.2:
+ dependencies:
+ defu: 6.1.4
+ destr: 2.0.5
+
+ react-dom@19.2.4(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+ scheduler: 0.27.0
+
+ react-is@16.13.1: {}
+
+ react@19.2.4: {}
+
+ read-cache@1.0.0:
+ dependencies:
+ pify: 2.3.0
+
+ readdirp@3.6.0:
+ dependencies:
+ picomatch: 2.3.1
+
+ readdirp@4.1.2: {}
+
+ redent@3.0.0:
+ dependencies:
+ indent-string: 4.0.0
+ strip-indent: 3.0.0
+
+ reflect.getprototypeof@1.0.10:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ which-builtin-type: 1.2.1
+
+ regexp.prototype.flags@1.5.4:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ set-function-name: 2.0.2
+
+ require-directory@2.1.1: {}
+
+ require-main-filename@2.0.0: {}
+
+ resolve-from@4.0.0: {}
+
+ resolve-from@5.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ resolve@1.22.11:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ resolve@2.0.0-next.6:
+ dependencies:
+ es-errors: 1.3.0
+ is-core-module: 2.16.1
+ node-exports-info: 1.6.0
+ object-keys: 1.1.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ reusify@1.1.0: {}
+
+ rollup@4.59.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.59.0
+ '@rollup/rollup-android-arm64': 4.59.0
+ '@rollup/rollup-darwin-arm64': 4.59.0
+ '@rollup/rollup-darwin-x64': 4.59.0
+ '@rollup/rollup-freebsd-arm64': 4.59.0
+ '@rollup/rollup-freebsd-x64': 4.59.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.59.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.59.0
+ '@rollup/rollup-linux-arm64-gnu': 4.59.0
+ '@rollup/rollup-linux-arm64-musl': 4.59.0
+ '@rollup/rollup-linux-loong64-gnu': 4.59.0
+ '@rollup/rollup-linux-loong64-musl': 4.59.0
+ '@rollup/rollup-linux-ppc64-gnu': 4.59.0
+ '@rollup/rollup-linux-ppc64-musl': 4.59.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.59.0
+ '@rollup/rollup-linux-riscv64-musl': 4.59.0
+ '@rollup/rollup-linux-s390x-gnu': 4.59.0
+ '@rollup/rollup-linux-x64-gnu': 4.59.0
+ '@rollup/rollup-linux-x64-musl': 4.59.0
+ '@rollup/rollup-openbsd-x64': 4.59.0
+ '@rollup/rollup-openharmony-arm64': 4.59.0
+ '@rollup/rollup-win32-arm64-msvc': 4.59.0
+ '@rollup/rollup-win32-ia32-msvc': 4.59.0
+ '@rollup/rollup-win32-x64-gnu': 4.59.0
+ '@rollup/rollup-win32-x64-msvc': 4.59.0
+ fsevents: 2.3.3
+
+ rrweb-cssom@0.8.0: {}
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ safe-array-concat@1.1.3:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ has-symbols: 1.1.0
+ isarray: 2.0.5
+
+ safe-push-apply@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ isarray: 2.0.5
+
+ safe-regex-test@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-regex: 1.2.1
+
+ safer-buffer@2.1.2: {}
+
+ saxes@6.0.0:
+ dependencies:
+ xmlchars: 2.2.0
+
+ scheduler@0.27.0: {}
+
+ semver@6.3.1: {}
+
+ semver@7.7.4: {}
+
+ set-blocking@2.0.0: {}
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+
+ set-function-name@2.0.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+
+ set-proto@1.0.0:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+
+ sharp@0.34.5:
+ dependencies:
+ '@img/colour': 1.1.0
+ detect-libc: 2.1.2
+ semver: 7.7.4
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.34.5
+ '@img/sharp-darwin-x64': 0.34.5
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ '@img/sharp-linux-arm': 0.34.5
+ '@img/sharp-linux-arm64': 0.34.5
+ '@img/sharp-linux-ppc64': 0.34.5
+ '@img/sharp-linux-riscv64': 0.34.5
+ '@img/sharp-linux-s390x': 0.34.5
+ '@img/sharp-linux-x64': 0.34.5
+ '@img/sharp-linuxmusl-arm64': 0.34.5
+ '@img/sharp-linuxmusl-x64': 0.34.5
+ '@img/sharp-wasm32': 0.34.5
+ '@img/sharp-win32-arm64': 0.34.5
+ '@img/sharp-win32-ia32': 0.34.5
+ '@img/sharp-win32-x64': 0.34.5
+ optional: true
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ side-channel-list@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.0
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ siginfo@2.0.0: {}
+
+ siwe@2.3.2(ethers@6.16.0):
+ dependencies:
+ '@spruceid/siwe-parser': 2.1.2
+ '@stablelib/random': 1.0.2
+ ethers: 6.16.0
+ uri-js: 4.4.1
+ valid-url: 1.0.9
+
+ smol-toml@1.6.0: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map@0.6.1: {}
+
+ stable-hash@0.0.5: {}
+
+ stackback@0.0.2: {}
+
+ std-env@3.10.0: {}
+
+ stop-iteration-iterator@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ internal-slot: 1.1.0
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string.prototype.includes@2.0.1:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+
+ string.prototype.matchall@4.0.12:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ regexp.prototype.flags: 1.5.4
+ set-function-name: 2.0.2
+ side-channel: 1.1.0
+
+ string.prototype.repeat@1.0.0:
+ dependencies:
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+
+ string.prototype.trim@1.2.10:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-data-property: 1.1.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+ has-property-descriptors: 1.0.2
+
+ string.prototype.trimend@1.0.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ string.prototype.trimstart@1.0.8:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-bom@3.0.0: {}
+
+ strip-indent@3.0.0:
+ dependencies:
+ min-indent: 1.0.1
+
+ strip-json-comments@3.1.1: {}
+
+ strip-literal@3.1.0:
+ dependencies:
+ js-tokens: 9.0.1
+
+ styled-jsx@5.1.6(react@19.2.4):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.4
+
+ sucrase@3.35.1:
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ commander: 4.1.1
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.7
+ tinyglobby: 0.2.15
+ ts-interface-checker: 0.1.13
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ symbol-tree@3.2.4: {}
+
+ tailwindcss@3.4.19(yaml@2.8.2):
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.7
+ lilconfig: 3.1.3
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.8
+ postcss-import: 15.1.0(postcss@8.5.8)
+ postcss-js: 4.1.0(postcss@8.5.8)
+ postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.8)(yaml@2.8.2)
+ postcss-nested: 6.2.0(postcss@8.5.8)
+ postcss-selector-parser: 6.1.2
+ resolve: 1.22.11
+ sucrase: 3.35.1
+ transitivePeerDependencies:
+ - tsx
+ - yaml
+
+ thenify-all@1.6.0:
+ dependencies:
+ thenify: 3.3.1
+
+ thenify@3.3.1:
+ dependencies:
+ any-promise: 1.3.0
+
+ tinybench@2.9.0: {}
+
+ tinyexec@0.3.2: {}
+
+ tinyexec@1.0.2: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tinypool@1.1.1: {}
+
+ tinyrainbow@2.0.0: {}
+
+ tinyspy@4.0.4: {}
+
+ tldts-core@6.1.86: {}
+
+ tldts@6.1.86:
+ dependencies:
+ tldts-core: 6.1.86
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ tough-cookie@5.1.2:
+ dependencies:
+ tldts: 6.1.86
+
+ tr46@5.1.1:
+ dependencies:
+ punycode: 2.3.1
+
+ ts-api-utils@2.4.0(typescript@5.9.3):
+ dependencies:
+ typescript: 5.9.3
+
+ ts-interface-checker@0.1.13: {}
+
+ ts-node@10.9.2(@types/node@24.12.0)(typescript@5.9.3):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.12
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 24.12.0
+ acorn: 8.16.0
+ acorn-walk: 8.3.5
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.4
+ make-error: 1.3.6
+ typescript: 5.9.3
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+
+ tsconfig-paths@3.15.0:
+ dependencies:
+ '@types/json5': 0.0.29
+ json5: 1.0.2
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
+ tslib@2.7.0: {}
+
+ tslib@2.8.1: {}
+
+ tweetnacl@1.0.3: {}
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ typed-array-buffer@1.0.3:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-length@1.0.3:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-offset@1.0.4:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+ reflect.getprototypeof: 1.0.10
+
+ typed-array-length@1.0.7:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ is-typed-array: 1.1.15
+ possible-typed-array-names: 1.1.0
+ reflect.getprototypeof: 1.0.10
+
+ typescript-eslint@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3):
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
+ eslint: 9.39.4(jiti@1.21.7)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ typescript@5.9.3: {}
+
+ uglify-js@3.19.3:
+ optional: true
+
+ unbox-primitive@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-bigints: 1.1.0
+ has-symbols: 1.1.0
+ which-boxed-primitive: 1.1.1
+
+ undici-types@6.19.8: {}
+
+ undici-types@7.16.0: {}
+
+ unrs-resolver@1.11.1:
+ dependencies:
+ napi-postinstall: 0.3.4
+ optionalDependencies:
+ '@unrs/resolver-binding-android-arm-eabi': 1.11.1
+ '@unrs/resolver-binding-android-arm64': 1.11.1
+ '@unrs/resolver-binding-darwin-arm64': 1.11.1
+ '@unrs/resolver-binding-darwin-x64': 1.11.1
+ '@unrs/resolver-binding-freebsd-x64': 1.11.1
+ '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1
+ '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1
+ '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-arm64-musl': 1.11.1
+ '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1
+ '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-x64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-x64-musl': 1.11.1
+ '@unrs/resolver-binding-wasm32-wasi': 1.11.1
+ '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1
+ '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
+ '@unrs/resolver-binding-win32-x64-msvc': 1.11.1
+
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ util-deprecate@1.0.2: {}
+
+ v8-compile-cache-lib@3.0.1: {}
+
+ valid-url@1.0.9: {}
+
+ viem@2.47.1(typescript@5.9.3)(zod@3.25.76):
+ dependencies:
+ '@noble/curves': 1.9.1
+ '@noble/hashes': 1.8.0
+ '@scure/bip32': 1.7.0
+ '@scure/bip39': 1.6.0
+ abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76)
+ isows: 1.0.7(ws@8.18.3)
+ ox: 0.14.0(typescript@5.9.3)(zod@3.25.76)
+ ws: 8.18.3
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+ - zod
+
+ vite-node@3.2.4(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.2):
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.3
+ es-module-lexer: 1.7.0
+ pathe: 2.0.3
+ vite: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.2)
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.2):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.8
+ rollup: 4.59.0
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 24.12.0
+ fsevents: 2.3.3
+ jiti: 1.21.7
+ yaml: 2.8.2
+
+ vitest@3.2.4(@types/node@24.12.0)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.2):
+ dependencies:
+ '@types/chai': 5.2.3
+ '@vitest/expect': 3.2.4
+ '@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.2))
+ '@vitest/pretty-format': 3.2.4
+ '@vitest/runner': 3.2.4
+ '@vitest/snapshot': 3.2.4
+ '@vitest/spy': 3.2.4
+ '@vitest/utils': 3.2.4
+ chai: 5.3.3
+ debug: 4.4.3
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ pathe: 2.0.3
+ picomatch: 4.0.3
+ std-env: 3.10.0
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.15
+ tinypool: 1.1.1
+ tinyrainbow: 2.0.0
+ vite: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.2)
+ vite-node: 3.2.4(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.2)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 24.12.0
+ jsdom: 26.1.0
+ transitivePeerDependencies:
+ - jiti
+ - less
+ - lightningcss
+ - msw
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ vscode-languageserver-textdocument@1.0.12: {}
+
+ vscode-uri@3.1.0: {}
+
+ w3c-xmlserializer@5.0.0:
+ dependencies:
+ xml-name-validator: 5.0.0
+
+ webidl-conversions@7.0.0: {}
+
+ whatwg-encoding@3.1.1:
+ dependencies:
+ iconv-lite: 0.6.3
+
+ whatwg-mimetype@4.0.0: {}
+
+ whatwg-url@14.2.0:
+ dependencies:
+ tr46: 5.1.1
+ webidl-conversions: 7.0.0
+
+ which-boxed-primitive@1.1.1:
+ dependencies:
+ is-bigint: 1.1.0
+ is-boolean-object: 1.2.2
+ is-number-object: 1.1.1
+ is-string: 1.1.1
+ is-symbol: 1.1.1
+
+ which-builtin-type@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ function.prototype.name: 1.1.8
+ has-tostringtag: 1.0.2
+ is-async-function: 2.1.1
+ is-date-object: 1.1.0
+ is-finalizationregistry: 1.1.1
+ is-generator-function: 1.1.2
+ is-regex: 1.2.1
+ is-weakref: 1.1.1
+ isarray: 2.0.5
+ which-boxed-primitive: 1.1.1
+ which-collection: 1.0.2
+ which-typed-array: 1.1.20
+
+ which-collection@1.0.2:
+ dependencies:
+ is-map: 2.0.3
+ is-set: 2.0.3
+ is-weakmap: 2.0.2
+ is-weakset: 2.0.4
+
+ which-module@2.0.1: {}
+
+ which-typed-array@1.1.20:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ for-each: 0.3.5
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ word-wrap@1.2.5: {}
+
+ wordwrap@1.0.0: {}
+
+ wrap-ansi@6.2.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ ws@8.17.1: {}
+
+ ws@8.18.3: {}
+
+ ws@8.19.0: {}
+
+ xdg-basedir@5.1.0: {}
+
+ xml-name-validator@5.0.0: {}
+
+ xmlchars@2.2.0: {}
+
+ y18n@4.0.3: {}
+
+ yaml@2.8.2: {}
+
+ yargs-parser@18.1.3:
+ dependencies:
+ camelcase: 5.3.1
+ decamelize: 1.2.0
+
+ yargs@15.4.1:
+ dependencies:
+ cliui: 6.0.0
+ decamelize: 1.2.0
+ find-up: 4.1.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ require-main-filename: 2.0.0
+ set-blocking: 2.0.0
+ string-width: 4.2.3
+ which-module: 2.0.1
+ y18n: 4.0.3
+ yargs-parser: 18.1.3
+
+ yn@3.1.1: {}
+
+ yocto-queue@0.1.0: {}
+
+ zod@3.25.76: {}
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 0000000..d0c615b
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,9 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
+
+export default config
diff --git a/prettier.config.mjs b/prettier.config.mjs
new file mode 100644
index 0000000..e769a57
--- /dev/null
+++ b/prettier.config.mjs
@@ -0,0 +1,19 @@
+/**
+ * @see https://prettier.io/docs/configuration
+ * @type {import("prettier").Config}
+ */
+const config = {
+ printWidth: 96,
+ arrowParens: 'avoid',
+ singleQuote: true,
+ tabWidth: 2,
+ useTabs: false,
+ semi: false,
+ trailingComma: 'es5',
+ bracketSpacing: true,
+ objectWrap: 'preserve',
+ bracketSameLine: false,
+ jsxSingleQuote: false,
+}
+
+export default config
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
new file mode 100644
index 0000000..24b3516
--- /dev/null
+++ b/prisma/schema.prisma
@@ -0,0 +1,29 @@
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+ directUrl = env("DIRECT_URL")
+}
+
+model Claim {
+ id String @id @default(cuid())
+ nullifierHash String @unique
+ agentAddress String
+ txHash String
+ amount String
+ claimedAt DateTime @default(now())
+}
+
+model Challenge {
+ id String @id @default(cuid())
+ nonce String @unique
+ domain String
+ uri String
+ chainId String
+ createdAt DateTime @default(now())
+ expiresAt DateTime
+ used Boolean @default(false)
+}
diff --git a/prisma/seed.ts b/prisma/seed.ts
new file mode 100644
index 0000000..3fa6ff9
--- /dev/null
+++ b/prisma/seed.ts
@@ -0,0 +1,73 @@
+import { PrismaClient } from '@prisma/client'
+
+const prisma = new PrismaClient()
+
+async function main() {
+ console.log('Seeding database...')
+
+ await prisma.claim.deleteMany()
+ await prisma.challenge.deleteMany()
+
+ const claims = await Promise.all([
+ prisma.claim.create({
+ data: {
+ nullifierHash: '0x1a2b3c4d5e6f708192a3b4c5d6e7f80011223344',
+ agentAddress: '0xbEBB5B46fFDA7E7494595E826FC4D4a61ce5f6A6',
+ txHash: '0xabc123def456789012345678901234567890abcdef1234567890abcdef123456',
+ amount: '1000000000000000000',
+ },
+ }),
+ prisma.claim.create({
+ data: {
+ nullifierHash: '0x2b3c4d5e6f708192a3b4c5d6e7f800112233445566',
+ agentAddress: '0x00d01CE88eA613281800E84b6C53Dfea58875d29',
+ txHash: '0xdef456789012345678901234567890abcdef1234567890abcdef123456789abc',
+ amount: '1000000000000000000',
+ },
+ }),
+ prisma.claim.create({
+ data: {
+ nullifierHash: '0x3c4d5e6f708192a3b4c5d6e7f8001122334455667788',
+ agentAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18',
+ txHash: '0x789012345678901234567890abcdef1234567890abcdef123456789abcdef012',
+ amount: '1000000000000000000',
+ },
+ }),
+ ])
+
+ const now = new Date()
+ const challenges = await Promise.all([
+ prisma.challenge.create({
+ data: {
+ nonce: 'abc123def456',
+ domain: 'localhost:3000',
+ uri: 'https://localhost:3000',
+ chainId: 'eip155:8453',
+ expiresAt: new Date(now.getTime() + 5 * 60 * 1000),
+ used: false,
+ },
+ }),
+ prisma.challenge.create({
+ data: {
+ nonce: 'xyz789ghi012',
+ domain: 'localhost:3000',
+ uri: 'https://localhost:3000',
+ chainId: 'eip155:8453',
+ expiresAt: new Date(now.getTime() - 10 * 60 * 1000),
+ used: true,
+ },
+ }),
+ ])
+
+ console.log(`Created ${claims.length} claims and ${challenges.length} challenges`)
+ console.log('Seed complete.')
+}
+
+main()
+ .catch(e => {
+ console.error(e)
+ process.exit(1)
+ })
+ .finally(async () => {
+ await prisma.$disconnect()
+ })
diff --git a/scripts/deploy-agentbook.sh b/scripts/deploy-agentbook.sh
new file mode 100755
index 0000000..0f807a9
--- /dev/null
+++ b/scripts/deploy-agentbook.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+set -euo pipefail
+
+# Deploy AgentBook to Base mainnet
+#
+# Prerequisites:
+# - Wallet must have ETH on Base mainnet
+# - Run from repo root or sybil-airdrop directory
+
+PRIVATE_KEY="0x01da274469d86f517d0274c9bcf026b531874458fee7304812bb3210116104b5"
+RPC_URL="https://mainnet.base.org"
+CONTRACTS_DIR="$(dirname "$0")/../../contracts"
+
+# World ID configuration
+WORLD_ID_ROUTER="0xBCC7e5910178AFFEEeBA573ba6903E9869594163"
+GROUP_ID="1"
+APP_ID="app_51925df0f1fa5388389b749e6a0f576c"
+ACTION="claim-token-dropping-air"
+
+echo "=== Deploying AgentBook ==="
+echo "World ID Router: $WORLD_ID_ROUTER"
+echo "Group ID: $GROUP_ID"
+echo "App ID: $APP_ID"
+echo "Action: $ACTION"
+
+cd "$CONTRACTS_DIR"
+
+WORLD_ID_ROUTER="$WORLD_ID_ROUTER" \
+GROUP_ID="$GROUP_ID" \
+APP_ID="$APP_ID" \
+ACTION="$ACTION" \
+forge script script/DeployAgentBook.s.sol:DeployAgentBook \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY" \
+ --broadcast \
+ -vvvv
+
+echo ""
+echo "=== AgentBook deployment complete ==="
+echo "Check the output above for the deployed address."
+echo "Update AGENTBOOK_CONTRACT_ADDRESS in your .env file."
diff --git a/scripts/deploy-all.sh b/scripts/deploy-all.sh
new file mode 100755
index 0000000..f5bcc10
--- /dev/null
+++ b/scripts/deploy-all.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+set -euo pipefail
+
+# ──────────────────────────────────────────────────────────
+# Dropping Air — Deploy all contracts to Base mainnet
+#
+# Deployer: 0xbEBB5B46fFDA7E7494595E826FC4D4a61ce5f6A6
+#
+# Usage:
+# DEPLOYER_PRIVATE_KEY=0x... bash scripts/deploy-all.sh
+# ──────────────────────────────────────────────────────────
+
+if [ -z "${DEPLOYER_PRIVATE_KEY:-}" ]; then
+ echo "Error: DEPLOYER_PRIVATE_KEY is not set."
+ echo ""
+ echo "Usage:"
+ echo " cd sybil-airdrop"
+ echo " DEPLOYER_PRIVATE_KEY=0x... bash scripts/deploy-all.sh"
+ exit 1
+fi
+
+RPC_URL="https://mainnet.base.org"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+SYBIL_CONTRACTS_DIR="$SCRIPT_DIR/../contracts"
+AGENTBOOK_CONTRACTS_DIR="$SCRIPT_DIR/../../contracts"
+
+# Pre-computed externalNullifierHash for:
+# app_id = app_51925df0f1fa5388389b749e6a0f576c
+# action = claim-token-dropping-air
+WORLD_ID_ROUTER="0xBCC7e5910178AFFEEeBA573ba6903E9869594163"
+GROUP_ID="1"
+EXTERNAL_NULLIFIER_HASH="328856683023252186786757582795161293175094147904016535319765956645134055853"
+
+CLAIM_AMOUNT="1000000000000000000" # 1 DROP (18 decimals)
+TOTAL_SUPPLY="1000000000000000000000000" # 1,000,000 DROP
+FUND_AMOUNT="10000000000000000000000" # 10,000 DROP to seed
+
+DEPLOYER_ADDRESS=$(cast wallet address "$DEPLOYER_PRIVATE_KEY")
+
+echo ""
+echo "╔══════════════════════════════════════════╗"
+echo "║ Dropping Air — Contract Deployment ║"
+echo "╚══════════════════════════════════════════╝"
+echo ""
+echo "Network: Base mainnet (8453)"
+echo "Deployer: $DEPLOYER_ADDRESS"
+echo "Balance: $(cast balance "$DEPLOYER_ADDRESS" --rpc-url "$RPC_URL" --ether) ETH"
+echo ""
+
+# ── 1. AgentBook ────────────────────────────────────────
+echo "━━━ [1/4] Deploying AgentBook ━━━"
+cd "$AGENTBOOK_CONTRACTS_DIR"
+
+AGENTBOOK_OUTPUT=$(forge create src/AgentBook.sol:AgentBook \
+ --constructor-args "$WORLD_ID_ROUTER" "$GROUP_ID" "$EXTERNAL_NULLIFIER_HASH" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$DEPLOYER_PRIVATE_KEY" \
+ --json)
+AGENTBOOK_ADDRESS=$(echo "$AGENTBOOK_OUTPUT" | jq -r '.deployedTo')
+echo " AgentBook: $AGENTBOOK_ADDRESS"
+echo ""
+
+# ── 2. DroppingAirToken ────────────────────────────────
+echo "━━━ [2/4] Deploying DroppingAirToken (DROP) ━━━"
+cd "$SYBIL_CONTRACTS_DIR"
+
+TOKEN_OUTPUT=$(forge create src/DroppingAirToken.sol:DroppingAirToken \
+ --constructor-args "$TOTAL_SUPPLY" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$DEPLOYER_PRIVATE_KEY" \
+ --json)
+TOKEN_ADDRESS=$(echo "$TOKEN_OUTPUT" | jq -r '.deployedTo')
+echo " Token: $TOKEN_ADDRESS"
+echo ""
+
+# ── 3. SybilAirdrop ────────────────────────────────────
+echo "━━━ [3/4] Deploying SybilAirdrop ━━━"
+
+AIRDROP_OUTPUT=$(forge create src/SybilAirdrop.sol:SybilAirdrop \
+ --constructor-args "$TOKEN_ADDRESS" "$CLAIM_AMOUNT" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$DEPLOYER_PRIVATE_KEY" \
+ --json)
+AIRDROP_ADDRESS=$(echo "$AIRDROP_OUTPUT" | jq -r '.deployedTo')
+echo " Airdrop: $AIRDROP_ADDRESS"
+echo ""
+
+# ── 4. Fund airdrop ────────────────────────────────────
+echo "━━━ [4/4] Funding SybilAirdrop with 10,000 DROP ━━━"
+
+cast send "$TOKEN_ADDRESS" \
+ "transfer(address,uint256)" "$AIRDROP_ADDRESS" "$FUND_AMOUNT" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$DEPLOYER_PRIVATE_KEY" \
+ --json > /dev/null
+
+echo " Done."
+echo ""
+
+# ── Summary ─────────────────────────────────────────────
+echo "╔══════════════════════════════════════════╗"
+echo "║ Deployment Complete! ║"
+echo "╚══════════════════════════════════════════╝"
+echo ""
+echo "Add these to sybil-airdrop/.env:"
+echo ""
+echo " AGENTBOOK_CONTRACT_ADDRESS=$AGENTBOOK_ADDRESS"
+echo " NEXT_PUBLIC_AGENTBOOK_ADDRESS=$AGENTBOOK_ADDRESS"
+echo " AIRDROP_CONTRACT_ADDRESS=$AIRDROP_ADDRESS"
+echo " TOKEN_ADDRESS=$TOKEN_ADDRESS"
+echo " AIRDROP_SIGNER_PRIVATE_KEY=\$DEPLOYER_PRIVATE_KEY"
+echo ""
+echo "The deployer ($DEPLOYER_ADDRESS) is the SybilAirdrop owner."
+echo "Its private key must be set as AIRDROP_SIGNER_PRIVATE_KEY."
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
new file mode 100755
index 0000000..7634fc5
--- /dev/null
+++ b/scripts/deploy.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+set -euo pipefail
+
+# Dropping Air — Full deployment script for Base mainnet
+# Deploys: DroppingAirToken, SybilAirdrop, then funds the airdrop contract
+#
+# Prerequisites:
+# - Wallet 0x00d01CE88eA613281800E84b6C53Dfea58875d29 must have ETH on Base mainnet
+# - Run from the sybil-airdrop directory
+
+PRIVATE_KEY="0x01da274469d86f517d0274c9bcf026b531874458fee7304812bb3210116104b5"
+RPC_URL="https://mainnet.base.org"
+CONTRACTS_DIR="$(dirname "$0")/../contracts"
+
+CLAIM_AMOUNT="1000000000000000000" # 1 token (18 decimals)
+TOTAL_SUPPLY="1000000000000000000000000" # 1,000,000 tokens
+
+echo "=== Deploying DroppingAirToken ==="
+TOKEN_OUTPUT=$(forge create "$CONTRACTS_DIR/src/DroppingAirToken.sol:DroppingAirToken" \
+ --constructor-args "$TOTAL_SUPPLY" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY" \
+ --json)
+TOKEN_ADDRESS=$(echo "$TOKEN_OUTPUT" | jq -r '.deployedTo')
+echo "Token deployed at: $TOKEN_ADDRESS"
+
+echo ""
+echo "=== Deploying SybilAirdrop ==="
+AIRDROP_OUTPUT=$(forge create "$CONTRACTS_DIR/src/SybilAirdrop.sol:SybilAirdrop" \
+ --constructor-args "$TOKEN_ADDRESS" "$CLAIM_AMOUNT" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY" \
+ --json)
+AIRDROP_ADDRESS=$(echo "$AIRDROP_OUTPUT" | jq -r '.deployedTo')
+echo "SybilAirdrop deployed at: $AIRDROP_ADDRESS"
+
+echo ""
+echo "=== Funding SybilAirdrop with 10,000 tokens ==="
+FUND_AMOUNT="10000000000000000000000" # 10,000 tokens
+cast send "$TOKEN_ADDRESS" \
+ "transfer(address,uint256)" "$AIRDROP_ADDRESS" "$FUND_AMOUNT" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY"
+
+echo ""
+echo "=== Deployment Complete ==="
+echo "TOKEN_ADDRESS=$TOKEN_ADDRESS"
+echo "AIRDROP_CONTRACT_ADDRESS=$AIRDROP_ADDRESS"
+echo ""
+echo "Update your .env file with these values."
diff --git a/sybil-airdrop/.env.example b/sybil-airdrop/.env.example
new file mode 100644
index 0000000..6a1c14c
--- /dev/null
+++ b/sybil-airdrop/.env.example
@@ -0,0 +1,23 @@
+# AgentBook
+AGENTBOOK_CONTRACT_ADDRESS=0x...
+AGENTBOOK_RPC_URL=https://mainnet.base.org
+NEXT_PUBLIC_AGENTBOOK_ADDRESS=0x...
+
+# Airdrop Contract
+AIRDROP_CONTRACT_ADDRESS=0x...
+AIRDROP_SIGNER_PRIVATE_KEY=0x...
+
+# Token
+TOKEN_ADDRESS=0x...
+CLAIM_AMOUNT=1000000000000000000
+
+# World ID RP
+RP_SIGNING_KEY=0x...
+RP_ID=rp_...
+
+# App
+NEXT_PUBLIC_CHAIN_ID=eip155:8453
+NEXT_PUBLIC_DOMAIN=localhost:3000
+
+# Database (Supabase PostgreSQL)
+DATABASE_URL=postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres
diff --git a/sybil-airdrop/.gitignore b/sybil-airdrop/.gitignore
new file mode 100644
index 0000000..736b242
--- /dev/null
+++ b/sybil-airdrop/.gitignore
@@ -0,0 +1,18 @@
+.env
+.env.local
+.next/
+node_modules/
+cache/
+contracts/cache/
+contracts/out/
+tsconfig.tsbuildinfo
+*.db
+
+# Playwright
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
+/playwright/.auth/
+
+.vercel
diff --git a/sybil-airdrop/README.md b/sybil-airdrop/README.md
new file mode 100644
index 0000000..66f1fbe
--- /dev/null
+++ b/sybil-airdrop/README.md
@@ -0,0 +1,94 @@
+# Sybil-Resistant Airdrop
+
+A token airdrop system that enforces one-claim-per-human using [AgentKit](../agentkit/) and World ID proof-of-personhood. Built on Base Sepolia.
+
+## How It Works
+
+1. Agents register their wallet in the **AgentBook** smart contract using a World ID proof, linking their address to an anonymous human identifier (nullifier hash).
+2. To claim, the agent signs a CAIP-122 challenge (SIWE format) proving wallet ownership.
+3. The server verifies the signature via AgentKit, looks up the human identifier in the AgentBook, and checks for duplicate claims.
+4. If eligible, the server calls the **SybilAirdrop** contract to transfer tokens.
+
+**Dual sybil protection**: the backend checks a database before submitting, and the contract itself rejects duplicate claims on-chain.
+
+## Architecture
+
+```
+Agent → GET /api/challenge → Server returns CAIP-122 challenge
+Agent → Signs challenge with wallet (SIWE)
+Agent → POST /api/claim { payload } → Server verifies → AgentBook lookup → Airdrop
+```
+
+## Setup
+
+### Prerequisites
+
+- Node.js 18+
+- A deployed AgentBook contract on Base Sepolia
+- A deployed SybilAirdrop contract (funded with ERC-20 tokens)
+- A backend signer wallet (owner of the SybilAirdrop contract)
+
+### Install
+
+```bash
+# From the repo root
+npm install
+
+# Generate Prisma client and create database
+cd sybil-airdrop
+npx prisma generate
+npx prisma db push
+```
+
+### Configure
+
+Copy `.env.example` to `.env` and fill in the values:
+
+```bash
+cp .env.example .env
+```
+
+### Run
+
+```bash
+npm run dev
+```
+
+Open [http://localhost:3000](http://localhost:3000).
+
+## Smart Contract
+
+`contracts/src/SybilAirdrop.sol` — an ERC-20 airdrop contract with per-human claim tracking:
+
+- `claim(address agent, uint256 nullifierHash)` — owner-only, transfers tokens to the agent
+- `hasClaimed(uint256 nullifierHash)` — check if a human already claimed
+- `remainingBalance()` — tokens left in the pool
+- `withdraw()` — owner reclaims unclaimed tokens
+
+Deploy with Foundry:
+
+```bash
+cd contracts
+forge create src/SybilAirdrop.sol:SybilAirdrop \
+ --constructor-args \
+ --rpc-url https://sepolia.base.org \
+ --private-key $DEPLOYER_KEY
+```
+
+Then fund the contract by transferring ERC-20 tokens to its address.
+
+## API
+
+| Endpoint | Method | Description |
+|---|---|---|
+| `/api/challenge` | GET | Generate a CAIP-122 challenge (nonce, domain, chain) |
+| `/api/claim` | POST | Submit signed payload to verify identity and claim tokens |
+| `/api/status?address=0x...` | GET | Check registration and claim status for an address |
+
+## Tech Stack
+
+- **Next.js 15** (App Router) with Tailwind CSS v3
+- **Prisma** with SQLite for claim tracking
+- **viem** for contract interactions
+- **@worldcoin/agentkit** for signature verification and AgentBook lookups
+- **Foundry** for the SybilAirdrop Solidity contract
diff --git a/sybil-airdrop/contracts/foundry.toml b/sybil-airdrop/contracts/foundry.toml
new file mode 100644
index 0000000..c1e903a
--- /dev/null
+++ b/sybil-airdrop/contracts/foundry.toml
@@ -0,0 +1,4 @@
+[profile.default]
+src = "src"
+out = "out"
+libs = ["../../node_modules"]
diff --git a/sybil-airdrop/contracts/src/DroppingAirToken.sol b/sybil-airdrop/contracts/src/DroppingAirToken.sol
new file mode 100644
index 0000000..cc6e545
--- /dev/null
+++ b/sybil-airdrop/contracts/src/DroppingAirToken.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract DroppingAirToken is ERC20 {
+ constructor(uint256 initialSupply) ERC20("Dropping Air", "DROP") {
+ _mint(msg.sender, initialSupply);
+ }
+}
diff --git a/sybil-airdrop/contracts/src/SybilAirdrop.sol b/sybil-airdrop/contracts/src/SybilAirdrop.sol
new file mode 100644
index 0000000..6077f2e
--- /dev/null
+++ b/sybil-airdrop/contracts/src/SybilAirdrop.sol
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+
+contract SybilAirdrop is Ownable {
+ IERC20 public immutable token;
+ uint256 public immutable claimAmount;
+ uint256 public totalClaims;
+
+ mapping(uint256 => bool) public hasClaimed;
+
+ event Claimed(address indexed agent, uint256 indexed nullifierHash, uint256 amount);
+
+ constructor(address _token, uint256 _claimAmount) Ownable(msg.sender) {
+ token = IERC20(_token);
+ claimAmount = _claimAmount;
+ }
+
+ function claim(address agent, uint256 nullifierHash) external onlyOwner {
+ require(!hasClaimed[nullifierHash], "Already claimed");
+ require(token.balanceOf(address(this)) >= claimAmount, "Insufficient funds");
+
+ hasClaimed[nullifierHash] = true;
+ totalClaims++;
+ require(token.transfer(agent, claimAmount), "Transfer failed");
+
+ emit Claimed(agent, nullifierHash, claimAmount);
+ }
+
+ function remainingBalance() external view returns (uint256) {
+ return token.balanceOf(address(this));
+ }
+
+ function withdraw() external onlyOwner {
+ uint256 balance = token.balanceOf(address(this));
+ require(token.transfer(owner(), balance), "Transfer failed");
+ }
+}
diff --git a/sybil-airdrop/design-tokens.json b/sybil-airdrop/design-tokens.json
new file mode 100644
index 0000000..8a9a586
--- /dev/null
+++ b/sybil-airdrop/design-tokens.json
@@ -0,0 +1,94 @@
+{
+ "$type": "designTokens",
+ "$description": "Dropping Air — Minimalist design system. Import via Tokens Studio for Figma.",
+ "color": {
+ "neutral": {
+ "0": { "$value": "#FFFFFF", "$type": "color" },
+ "50": { "$value": "#F9FAFB", "$type": "color" },
+ "100": { "$value": "#F3F4F6", "$type": "color" },
+ "200": { "$value": "#E5E7EB", "$type": "color" },
+ "300": { "$value": "#D1D5DB", "$type": "color" },
+ "400": { "$value": "#9CA3AF", "$type": "color" },
+ "500": { "$value": "#6B7280", "$type": "color" },
+ "600": { "$value": "#4B5563", "$type": "color" },
+ "700": { "$value": "#374151", "$type": "color" },
+ "800": { "$value": "#1F2937", "$type": "color" },
+ "900": { "$value": "#111827", "$type": "color" },
+ "950": { "$value": "#030712", "$type": "color" }
+ },
+ "primary": {
+ "50": { "$value": "#EFF6FF", "$type": "color" },
+ "100": { "$value": "#DBEAFE", "$type": "color" },
+ "500": { "$value": "#3B82F6", "$type": "color" },
+ "600": { "$value": "#2563EB", "$type": "color" },
+ "700": { "$value": "#1D4ED8", "$type": "color" }
+ },
+ "success": {
+ "50": { "$value": "#F0FDF4", "$type": "color" },
+ "500": { "$value": "#22C55E", "$type": "color" },
+ "700": { "$value": "#15803D", "$type": "color" }
+ },
+ "error": {
+ "50": { "$value": "#FEF2F2", "$type": "color" },
+ "500": { "$value": "#EF4444", "$type": "color" },
+ "700": { "$value": "#B91C1C", "$type": "color" }
+ },
+ "warning": {
+ "50": { "$value": "#FFFBEB", "$type": "color" },
+ "500": { "$value": "#F59E0B", "$type": "color" },
+ "700": { "$value": "#B45309", "$type": "color" }
+ },
+ "semantic": {
+ "bg": { "$value": "{color.neutral.0}", "$type": "color" },
+ "bg-subtle": { "$value": "{color.neutral.50}", "$type": "color" },
+ "surface": { "$value": "{color.neutral.0}", "$type": "color" },
+ "surface-raised": { "$value": "{color.neutral.50}", "$type": "color" },
+ "border": { "$value": "{color.neutral.200}", "$type": "color" },
+ "border-subtle": { "$value": "{color.neutral.100}", "$type": "color" },
+ "text": { "$value": "{color.neutral.900}", "$type": "color" },
+ "text-secondary": { "$value": "{color.neutral.500}", "$type": "color" },
+ "text-muted": { "$value": "{color.neutral.400}", "$type": "color" }
+ }
+ },
+ "fontFamily": {
+ "sans": { "$value": "Inter, system-ui, -apple-system, sans-serif", "$type": "fontFamily" },
+ "mono": { "$value": "JetBrains Mono, ui-monospace, monospace", "$type": "fontFamily" }
+ },
+ "fontSize": {
+ "xs": { "$value": "0.75rem", "$type": "dimension" },
+ "sm": { "$value": "0.875rem", "$type": "dimension" },
+ "base": { "$value": "1rem", "$type": "dimension" },
+ "lg": { "$value": "1.125rem", "$type": "dimension" },
+ "xl": { "$value": "1.25rem", "$type": "dimension" },
+ "2xl": { "$value": "1.5rem", "$type": "dimension" },
+ "3xl": { "$value": "1.875rem", "$type": "dimension" }
+ },
+ "fontWeight": {
+ "normal": { "$value": "400", "$type": "fontWeight" },
+ "medium": { "$value": "500", "$type": "fontWeight" },
+ "semibold": { "$value": "600", "$type": "fontWeight" }
+ },
+ "spacing": {
+ "1": { "$value": "0.25rem", "$type": "dimension" },
+ "2": { "$value": "0.5rem", "$type": "dimension" },
+ "3": { "$value": "0.75rem", "$type": "dimension" },
+ "4": { "$value": "1rem", "$type": "dimension" },
+ "5": { "$value": "1.25rem", "$type": "dimension" },
+ "6": { "$value": "1.5rem", "$type": "dimension" },
+ "8": { "$value": "2rem", "$type": "dimension" },
+ "10": { "$value": "2.5rem", "$type": "dimension" },
+ "12": { "$value": "3rem", "$type": "dimension" },
+ "16": { "$value": "4rem", "$type": "dimension" }
+ },
+ "borderRadius": {
+ "sm": { "$value": "0.375rem", "$type": "dimension" },
+ "md": { "$value": "0.5rem", "$type": "dimension" },
+ "lg": { "$value": "0.75rem", "$type": "dimension" },
+ "xl": { "$value": "1rem", "$type": "dimension" },
+ "full": { "$value": "9999px", "$type": "dimension" }
+ },
+ "shadow": {
+ "sm": { "$value": "0 1px 2px 0 rgba(0,0,0,0.05)", "$type": "boxShadow" },
+ "base": { "$value": "0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1)", "$type": "boxShadow" }
+ }
+}
diff --git a/sybil-airdrop/foundry.toml b/sybil-airdrop/foundry.toml
new file mode 100644
index 0000000..445f809
--- /dev/null
+++ b/sybil-airdrop/foundry.toml
@@ -0,0 +1,4 @@
+[profile.default]
+src = "contracts/src"
+out = "contracts/out"
+libs = ["../node_modules"]
diff --git a/sybil-airdrop/next-env.d.ts b/sybil-airdrop/next-env.d.ts
new file mode 100644
index 0000000..1b3be08
--- /dev/null
+++ b/sybil-airdrop/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/sybil-airdrop/next.config.ts b/sybil-airdrop/next.config.ts
new file mode 100644
index 0000000..f202467
--- /dev/null
+++ b/sybil-airdrop/next.config.ts
@@ -0,0 +1,7 @@
+import type { NextConfig } from 'next'
+
+const nextConfig: NextConfig = {
+ transpilePackages: ['@worldcoin/agentkit'],
+}
+
+export default nextConfig
diff --git a/sybil-airdrop/package.json b/sybil-airdrop/package.json
new file mode 100644
index 0000000..b3734c2
--- /dev/null
+++ b/sybil-airdrop/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "sybil-airdrop",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "db:generate": "prisma generate",
+ "db:push": "prisma db push",
+ "db:studio": "prisma studio",
+ "deploy:contracts": "bash scripts/deploy.sh",
+ "deploy:agentbook": "bash scripts/deploy-agentbook.sh",
+ "test": "playwright test",
+ "test:ui": "playwright test --ui"
+ },
+ "dependencies": {
+ "@openzeppelin/contracts": "^5.6.1",
+ "@opentelemetry/api": "^1.9.0",
+ "@prisma/client": "^6.4.1",
+ "@supabase/supabase-js": "^2.99.0",
+ "@worldcoin/agentkit": "^0.0.3",
+ "@worldcoin/idkit": "^4.0.8",
+ "next": "15.4.10",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "siwe": "^2.3.2",
+ "viem": "^2.23.5",
+ "zod": "^3.24.2"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.58.2",
+ "@types/node": "^20.17.22",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.5.3",
+ "prisma": "^6.4.1",
+ "tailwindcss": "^3.4.0",
+ "typescript": "^5.8.3"
+ }
+}
diff --git a/sybil-airdrop/playwright.config.ts b/sybil-airdrop/playwright.config.ts
new file mode 100644
index 0000000..f57114d
--- /dev/null
+++ b/sybil-airdrop/playwright.config.ts
@@ -0,0 +1,26 @@
+import { defineConfig, devices } from '@playwright/test'
+
+export default defineConfig({
+ testDir: './tests',
+ fullyParallel: true,
+ forbidOnly: !!process.env.CI,
+ retries: process.env.CI ? 2 : 0,
+ workers: process.env.CI ? 1 : undefined,
+ reporter: 'html',
+ use: {
+ baseURL: 'http://localhost:3000',
+ trace: 'on-first-retry',
+ },
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ ],
+ webServer: {
+ command: 'npm run dev',
+ url: 'http://localhost:3000',
+ reuseExistingServer: !process.env.CI,
+ timeout: 30000,
+ },
+})
diff --git a/sybil-airdrop/postcss.config.mjs b/sybil-airdrop/postcss.config.mjs
new file mode 100644
index 0000000..84ece2d
--- /dev/null
+++ b/sybil-airdrop/postcss.config.mjs
@@ -0,0 +1,9 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
+
+export default config
diff --git a/sybil-airdrop/prisma/schema.prisma b/sybil-airdrop/prisma/schema.prisma
new file mode 100644
index 0000000..24b3516
--- /dev/null
+++ b/sybil-airdrop/prisma/schema.prisma
@@ -0,0 +1,29 @@
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+ directUrl = env("DIRECT_URL")
+}
+
+model Claim {
+ id String @id @default(cuid())
+ nullifierHash String @unique
+ agentAddress String
+ txHash String
+ amount String
+ claimedAt DateTime @default(now())
+}
+
+model Challenge {
+ id String @id @default(cuid())
+ nonce String @unique
+ domain String
+ uri String
+ chainId String
+ createdAt DateTime @default(now())
+ expiresAt DateTime
+ used Boolean @default(false)
+}
diff --git a/sybil-airdrop/prisma/seed.ts b/sybil-airdrop/prisma/seed.ts
new file mode 100644
index 0000000..5d46c7a
--- /dev/null
+++ b/sybil-airdrop/prisma/seed.ts
@@ -0,0 +1,73 @@
+import { PrismaClient } from '@prisma/client'
+
+const prisma = new PrismaClient()
+
+async function main() {
+ console.log('Seeding database...')
+
+ await prisma.claim.deleteMany()
+ await prisma.challenge.deleteMany()
+
+ const claims = await Promise.all([
+ prisma.claim.create({
+ data: {
+ nullifierHash: '0x1a2b3c4d5e6f708192a3b4c5d6e7f80011223344',
+ agentAddress: '0xbEBB5B46fFDA7E7494595E826FC4D4a61ce5f6A6',
+ txHash: '0xabc123def456789012345678901234567890abcdef1234567890abcdef123456',
+ amount: '1000000000000000000',
+ },
+ }),
+ prisma.claim.create({
+ data: {
+ nullifierHash: '0x2b3c4d5e6f708192a3b4c5d6e7f800112233445566',
+ agentAddress: '0x00d01CE88eA613281800E84b6C53Dfea58875d29',
+ txHash: '0xdef456789012345678901234567890abcdef1234567890abcdef123456789abc',
+ amount: '1000000000000000000',
+ },
+ }),
+ prisma.claim.create({
+ data: {
+ nullifierHash: '0x3c4d5e6f708192a3b4c5d6e7f8001122334455667788',
+ agentAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18',
+ txHash: '0x789012345678901234567890abcdef1234567890abcdef123456789abcdef012',
+ amount: '1000000000000000000',
+ },
+ }),
+ ])
+
+ const now = new Date()
+ const challenges = await Promise.all([
+ prisma.challenge.create({
+ data: {
+ nonce: 'abc123def456',
+ domain: 'localhost:3000',
+ uri: 'https://localhost:3000',
+ chainId: 'eip155:8453',
+ expiresAt: new Date(now.getTime() + 5 * 60 * 1000),
+ used: false,
+ },
+ }),
+ prisma.challenge.create({
+ data: {
+ nonce: 'xyz789ghi012',
+ domain: 'localhost:3000',
+ uri: 'https://localhost:3000',
+ chainId: 'eip155:8453',
+ expiresAt: new Date(now.getTime() - 10 * 60 * 1000),
+ used: true,
+ },
+ }),
+ ])
+
+ console.log(`Created ${claims.length} claims and ${challenges.length} challenges`)
+ console.log('Seed complete.')
+}
+
+main()
+ .catch((e) => {
+ console.error(e)
+ process.exit(1)
+ })
+ .finally(async () => {
+ await prisma.$disconnect()
+ })
diff --git a/sybil-airdrop/scripts/deploy-agentbook.sh b/sybil-airdrop/scripts/deploy-agentbook.sh
new file mode 100755
index 0000000..0f807a9
--- /dev/null
+++ b/sybil-airdrop/scripts/deploy-agentbook.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+set -euo pipefail
+
+# Deploy AgentBook to Base mainnet
+#
+# Prerequisites:
+# - Wallet must have ETH on Base mainnet
+# - Run from repo root or sybil-airdrop directory
+
+PRIVATE_KEY="0x01da274469d86f517d0274c9bcf026b531874458fee7304812bb3210116104b5"
+RPC_URL="https://mainnet.base.org"
+CONTRACTS_DIR="$(dirname "$0")/../../contracts"
+
+# World ID configuration
+WORLD_ID_ROUTER="0xBCC7e5910178AFFEEeBA573ba6903E9869594163"
+GROUP_ID="1"
+APP_ID="app_51925df0f1fa5388389b749e6a0f576c"
+ACTION="claim-token-dropping-air"
+
+echo "=== Deploying AgentBook ==="
+echo "World ID Router: $WORLD_ID_ROUTER"
+echo "Group ID: $GROUP_ID"
+echo "App ID: $APP_ID"
+echo "Action: $ACTION"
+
+cd "$CONTRACTS_DIR"
+
+WORLD_ID_ROUTER="$WORLD_ID_ROUTER" \
+GROUP_ID="$GROUP_ID" \
+APP_ID="$APP_ID" \
+ACTION="$ACTION" \
+forge script script/DeployAgentBook.s.sol:DeployAgentBook \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY" \
+ --broadcast \
+ -vvvv
+
+echo ""
+echo "=== AgentBook deployment complete ==="
+echo "Check the output above for the deployed address."
+echo "Update AGENTBOOK_CONTRACT_ADDRESS in your .env file."
diff --git a/sybil-airdrop/scripts/deploy-all.sh b/sybil-airdrop/scripts/deploy-all.sh
new file mode 100755
index 0000000..f5bcc10
--- /dev/null
+++ b/sybil-airdrop/scripts/deploy-all.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+set -euo pipefail
+
+# ──────────────────────────────────────────────────────────
+# Dropping Air — Deploy all contracts to Base mainnet
+#
+# Deployer: 0xbEBB5B46fFDA7E7494595E826FC4D4a61ce5f6A6
+#
+# Usage:
+# DEPLOYER_PRIVATE_KEY=0x... bash scripts/deploy-all.sh
+# ──────────────────────────────────────────────────────────
+
+if [ -z "${DEPLOYER_PRIVATE_KEY:-}" ]; then
+ echo "Error: DEPLOYER_PRIVATE_KEY is not set."
+ echo ""
+ echo "Usage:"
+ echo " cd sybil-airdrop"
+ echo " DEPLOYER_PRIVATE_KEY=0x... bash scripts/deploy-all.sh"
+ exit 1
+fi
+
+RPC_URL="https://mainnet.base.org"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+SYBIL_CONTRACTS_DIR="$SCRIPT_DIR/../contracts"
+AGENTBOOK_CONTRACTS_DIR="$SCRIPT_DIR/../../contracts"
+
+# Pre-computed externalNullifierHash for:
+# app_id = app_51925df0f1fa5388389b749e6a0f576c
+# action = claim-token-dropping-air
+WORLD_ID_ROUTER="0xBCC7e5910178AFFEEeBA573ba6903E9869594163"
+GROUP_ID="1"
+EXTERNAL_NULLIFIER_HASH="328856683023252186786757582795161293175094147904016535319765956645134055853"
+
+CLAIM_AMOUNT="1000000000000000000" # 1 DROP (18 decimals)
+TOTAL_SUPPLY="1000000000000000000000000" # 1,000,000 DROP
+FUND_AMOUNT="10000000000000000000000" # 10,000 DROP to seed
+
+DEPLOYER_ADDRESS=$(cast wallet address "$DEPLOYER_PRIVATE_KEY")
+
+echo ""
+echo "╔══════════════════════════════════════════╗"
+echo "║ Dropping Air — Contract Deployment ║"
+echo "╚══════════════════════════════════════════╝"
+echo ""
+echo "Network: Base mainnet (8453)"
+echo "Deployer: $DEPLOYER_ADDRESS"
+echo "Balance: $(cast balance "$DEPLOYER_ADDRESS" --rpc-url "$RPC_URL" --ether) ETH"
+echo ""
+
+# ── 1. AgentBook ────────────────────────────────────────
+echo "━━━ [1/4] Deploying AgentBook ━━━"
+cd "$AGENTBOOK_CONTRACTS_DIR"
+
+AGENTBOOK_OUTPUT=$(forge create src/AgentBook.sol:AgentBook \
+ --constructor-args "$WORLD_ID_ROUTER" "$GROUP_ID" "$EXTERNAL_NULLIFIER_HASH" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$DEPLOYER_PRIVATE_KEY" \
+ --json)
+AGENTBOOK_ADDRESS=$(echo "$AGENTBOOK_OUTPUT" | jq -r '.deployedTo')
+echo " AgentBook: $AGENTBOOK_ADDRESS"
+echo ""
+
+# ── 2. DroppingAirToken ────────────────────────────────
+echo "━━━ [2/4] Deploying DroppingAirToken (DROP) ━━━"
+cd "$SYBIL_CONTRACTS_DIR"
+
+TOKEN_OUTPUT=$(forge create src/DroppingAirToken.sol:DroppingAirToken \
+ --constructor-args "$TOTAL_SUPPLY" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$DEPLOYER_PRIVATE_KEY" \
+ --json)
+TOKEN_ADDRESS=$(echo "$TOKEN_OUTPUT" | jq -r '.deployedTo')
+echo " Token: $TOKEN_ADDRESS"
+echo ""
+
+# ── 3. SybilAirdrop ────────────────────────────────────
+echo "━━━ [3/4] Deploying SybilAirdrop ━━━"
+
+AIRDROP_OUTPUT=$(forge create src/SybilAirdrop.sol:SybilAirdrop \
+ --constructor-args "$TOKEN_ADDRESS" "$CLAIM_AMOUNT" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$DEPLOYER_PRIVATE_KEY" \
+ --json)
+AIRDROP_ADDRESS=$(echo "$AIRDROP_OUTPUT" | jq -r '.deployedTo')
+echo " Airdrop: $AIRDROP_ADDRESS"
+echo ""
+
+# ── 4. Fund airdrop ────────────────────────────────────
+echo "━━━ [4/4] Funding SybilAirdrop with 10,000 DROP ━━━"
+
+cast send "$TOKEN_ADDRESS" \
+ "transfer(address,uint256)" "$AIRDROP_ADDRESS" "$FUND_AMOUNT" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$DEPLOYER_PRIVATE_KEY" \
+ --json > /dev/null
+
+echo " Done."
+echo ""
+
+# ── Summary ─────────────────────────────────────────────
+echo "╔══════════════════════════════════════════╗"
+echo "║ Deployment Complete! ║"
+echo "╚══════════════════════════════════════════╝"
+echo ""
+echo "Add these to sybil-airdrop/.env:"
+echo ""
+echo " AGENTBOOK_CONTRACT_ADDRESS=$AGENTBOOK_ADDRESS"
+echo " NEXT_PUBLIC_AGENTBOOK_ADDRESS=$AGENTBOOK_ADDRESS"
+echo " AIRDROP_CONTRACT_ADDRESS=$AIRDROP_ADDRESS"
+echo " TOKEN_ADDRESS=$TOKEN_ADDRESS"
+echo " AIRDROP_SIGNER_PRIVATE_KEY=\$DEPLOYER_PRIVATE_KEY"
+echo ""
+echo "The deployer ($DEPLOYER_ADDRESS) is the SybilAirdrop owner."
+echo "Its private key must be set as AIRDROP_SIGNER_PRIVATE_KEY."
diff --git a/sybil-airdrop/scripts/deploy.sh b/sybil-airdrop/scripts/deploy.sh
new file mode 100755
index 0000000..7634fc5
--- /dev/null
+++ b/sybil-airdrop/scripts/deploy.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+set -euo pipefail
+
+# Dropping Air — Full deployment script for Base mainnet
+# Deploys: DroppingAirToken, SybilAirdrop, then funds the airdrop contract
+#
+# Prerequisites:
+# - Wallet 0x00d01CE88eA613281800E84b6C53Dfea58875d29 must have ETH on Base mainnet
+# - Run from the sybil-airdrop directory
+
+PRIVATE_KEY="0x01da274469d86f517d0274c9bcf026b531874458fee7304812bb3210116104b5"
+RPC_URL="https://mainnet.base.org"
+CONTRACTS_DIR="$(dirname "$0")/../contracts"
+
+CLAIM_AMOUNT="1000000000000000000" # 1 token (18 decimals)
+TOTAL_SUPPLY="1000000000000000000000000" # 1,000,000 tokens
+
+echo "=== Deploying DroppingAirToken ==="
+TOKEN_OUTPUT=$(forge create "$CONTRACTS_DIR/src/DroppingAirToken.sol:DroppingAirToken" \
+ --constructor-args "$TOTAL_SUPPLY" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY" \
+ --json)
+TOKEN_ADDRESS=$(echo "$TOKEN_OUTPUT" | jq -r '.deployedTo')
+echo "Token deployed at: $TOKEN_ADDRESS"
+
+echo ""
+echo "=== Deploying SybilAirdrop ==="
+AIRDROP_OUTPUT=$(forge create "$CONTRACTS_DIR/src/SybilAirdrop.sol:SybilAirdrop" \
+ --constructor-args "$TOKEN_ADDRESS" "$CLAIM_AMOUNT" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY" \
+ --json)
+AIRDROP_ADDRESS=$(echo "$AIRDROP_OUTPUT" | jq -r '.deployedTo')
+echo "SybilAirdrop deployed at: $AIRDROP_ADDRESS"
+
+echo ""
+echo "=== Funding SybilAirdrop with 10,000 tokens ==="
+FUND_AMOUNT="10000000000000000000000" # 10,000 tokens
+cast send "$TOKEN_ADDRESS" \
+ "transfer(address,uint256)" "$AIRDROP_ADDRESS" "$FUND_AMOUNT" \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY"
+
+echo ""
+echo "=== Deployment Complete ==="
+echo "TOKEN_ADDRESS=$TOKEN_ADDRESS"
+echo "AIRDROP_CONTRACT_ADDRESS=$AIRDROP_ADDRESS"
+echo ""
+echo "Update your .env file with these values."
diff --git a/sybil-airdrop/src/app/api/challenge/route.ts b/sybil-airdrop/src/app/api/challenge/route.ts
new file mode 100644
index 0000000..33f5bf1
--- /dev/null
+++ b/sybil-airdrop/src/app/api/challenge/route.ts
@@ -0,0 +1,36 @@
+import { NextResponse } from 'next/server'
+import { generateNonce } from 'siwe'
+import db from '@/lib/db'
+import { env } from '@/lib/config'
+
+export async function GET() {
+ const nonce = generateNonce()
+ const domain = env.NEXT_PUBLIC_DOMAIN
+ const chainId = env.NEXT_PUBLIC_CHAIN_ID
+ const uri = `https://${domain}`
+ const now = new Date()
+ const expiresAt = new Date(now.getTime() + 5 * 60 * 1000)
+
+ const { error } = await db.from('Challenge').insert({
+ nonce,
+ domain,
+ uri,
+ chainId,
+ expiresAt: expiresAt.toISOString(),
+ })
+
+ if (error) {
+ console.error('Challenge creation error:', error)
+ return NextResponse.json({ error: 'Failed to create challenge' }, { status: 500 })
+ }
+
+ return NextResponse.json({
+ nonce,
+ domain,
+ uri,
+ chainId,
+ issuedAt: now.toISOString(),
+ expirationTime: expiresAt.toISOString(),
+ supportedChains: [chainId],
+ })
+}
diff --git a/sybil-airdrop/src/app/api/claim/route.ts b/sybil-airdrop/src/app/api/claim/route.ts
new file mode 100644
index 0000000..e06d541
--- /dev/null
+++ b/sybil-airdrop/src/app/api/claim/route.ts
@@ -0,0 +1,108 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { parseAgentkitHeader, validateAgentkitMessage, verifyAgentkitSignature, agentBook } from '@/lib/agentkit'
+import { executeAirdrop } from '@/lib/airdrop'
+import db from '@/lib/db'
+import type { Challenge, Claim } from '@/lib/db'
+import { env } from '@/lib/config'
+
+export async function POST(req: NextRequest) {
+ try {
+ const { payload } = await req.json()
+
+ if (!payload || typeof payload !== 'string') {
+ return NextResponse.json({ error: 'Missing or invalid payload' }, { status: 400 })
+ }
+
+ let parsed
+ try {
+ parsed = parseAgentkitHeader(payload)
+ } catch (err) {
+ return NextResponse.json(
+ { error: `Failed to parse payload: ${err instanceof Error ? err.message : 'unknown'}` },
+ { status: 400 }
+ )
+ }
+
+ const resourceUri = `https://${env.NEXT_PUBLIC_DOMAIN}`
+ const validation = await validateAgentkitMessage(parsed, resourceUri, {
+ checkNonce: async (nonce: string) => {
+ const { data: challenge } = await db
+ .from('Challenge')
+ .select('*')
+ .eq('nonce', nonce)
+ .single()
+
+ if (!challenge || challenge.used || new Date(challenge.expiresAt) < new Date()) return false
+
+ await db.from('Challenge').update({ used: true }).eq('nonce', nonce)
+ return true
+ },
+ })
+
+ if (!validation.valid) {
+ return NextResponse.json({ error: `Validation failed: ${validation.error}` }, { status: 400 })
+ }
+
+ const verification = await verifyAgentkitSignature(parsed)
+ if (!verification.valid || !verification.address) {
+ return NextResponse.json(
+ { error: `Signature verification failed: ${verification.error}` },
+ { status: 401 }
+ )
+ }
+
+ const agentAddress = verification.address as `0x${string}`
+
+ const nullifierHash = await agentBook.lookupHuman(agentAddress, env.NEXT_PUBLIC_CHAIN_ID)
+ if (!nullifierHash) {
+ return NextResponse.json(
+ { error: 'Agent is not registered in the AgentBook. Register with World ID first.' },
+ { status: 403 }
+ )
+ }
+
+ const { data: existingClaim } = await db
+ .from('Claim')
+ .select('*')
+ .eq('nullifierHash', nullifierHash)
+ .single()
+
+ if (existingClaim) {
+ return NextResponse.json(
+ {
+ error: 'This human has already claimed the airdrop',
+ txHash: existingClaim.txHash,
+ claimedAt: existingClaim.claimedAt,
+ },
+ { status: 409 }
+ )
+ }
+
+ const txHash = await executeAirdrop(agentAddress, BigInt(nullifierHash))
+
+ const { error: insertError } = await db.from('Claim').insert({
+ nullifierHash,
+ agentAddress,
+ txHash,
+ amount: env.CLAIM_AMOUNT,
+ })
+
+ if (insertError) {
+ console.error('Claim insert error:', insertError)
+ }
+
+ return NextResponse.json({
+ success: true,
+ txHash,
+ amount: env.CLAIM_AMOUNT,
+ agentAddress,
+ explorerUrl: `https://basescan.org/tx/${txHash}`,
+ })
+ } catch (error) {
+ console.error('Claim error:', error)
+ return NextResponse.json(
+ { error: `Internal server error: ${error instanceof Error ? error.message : 'unknown'}` },
+ { status: 500 }
+ )
+ }
+}
diff --git a/sybil-airdrop/src/app/api/rp-context/route.ts b/sybil-airdrop/src/app/api/rp-context/route.ts
new file mode 100644
index 0000000..34203f3
--- /dev/null
+++ b/sybil-airdrop/src/app/api/rp-context/route.ts
@@ -0,0 +1,28 @@
+import { NextResponse } from 'next/server'
+import { signRequest } from '@worldcoin/idkit/signing'
+
+const RP_SIGNING_KEY = process.env.RP_SIGNING_KEY!
+const RP_ID = process.env.RP_ID!
+const ACTION = 'claim-token-dropping-air'
+
+export async function GET() {
+ try {
+ const { sig, nonce, createdAt, expiresAt } = signRequest(ACTION, RP_SIGNING_KEY, 300)
+
+ return NextResponse.json({
+ rp_context: {
+ rp_id: RP_ID,
+ nonce,
+ created_at: createdAt,
+ expires_at: expiresAt,
+ signature: sig,
+ },
+ })
+ } catch (error) {
+ console.error('RP context signing error:', error)
+ return NextResponse.json(
+ { error: 'Failed to generate RP context' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/sybil-airdrop/src/app/api/status/route.ts b/sybil-airdrop/src/app/api/status/route.ts
new file mode 100644
index 0000000..0db0803
--- /dev/null
+++ b/sybil-airdrop/src/app/api/status/route.ts
@@ -0,0 +1,52 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { agentBook } from '@/lib/agentkit'
+import db from '@/lib/db'
+import type { Claim } from '@/lib/db'
+import { env } from '@/lib/config'
+
+export async function GET(req: NextRequest) {
+ const address = req.nextUrl.searchParams.get('address')
+
+ if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address)) {
+ return NextResponse.json({ error: 'Invalid or missing address parameter' }, { status: 400 })
+ }
+
+ try {
+ const nullifierHash = await agentBook.lookupHuman(address, env.NEXT_PUBLIC_CHAIN_ID)
+
+ if (!nullifierHash) {
+ return NextResponse.json({
+ registered: false,
+ humanId: null,
+ claimed: false,
+ claim: null,
+ })
+ }
+
+ const { data: claim } = await db
+ .from('Claim')
+ .select('*')
+ .eq('nullifierHash', nullifierHash)
+ .single()
+
+ return NextResponse.json({
+ registered: true,
+ humanId: `${nullifierHash.slice(0, 10)}...${nullifierHash.slice(-8)}`,
+ claimed: !!claim,
+ claim: claim
+ ? {
+ txHash: claim.txHash,
+ amount: claim.amount,
+ claimedAt: claim.claimedAt,
+ explorerUrl: `https://basescan.org/tx/${claim.txHash}`,
+ }
+ : null,
+ })
+ } catch (err) {
+ console.error('Status check error:', err)
+ return NextResponse.json(
+ { error: `Failed to check status: ${err instanceof Error ? err.message : 'unknown'}` },
+ { status: 500 }
+ )
+ }
+}
diff --git a/sybil-airdrop/src/app/globals.css b/sybil-airdrop/src/app/globals.css
new file mode 100644
index 0000000..ff4dcbf
--- /dev/null
+++ b/sybil-airdrop/src/app/globals.css
@@ -0,0 +1,10 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
+
+body {
+ background-color: #ffffff;
+ color: #111827;
+}
diff --git a/sybil-airdrop/src/app/layout.tsx b/sybil-airdrop/src/app/layout.tsx
new file mode 100644
index 0000000..c508331
--- /dev/null
+++ b/sybil-airdrop/src/app/layout.tsx
@@ -0,0 +1,38 @@
+import type { Metadata } from 'next'
+import './globals.css'
+
+export const metadata: Metadata = {
+ title: 'Dropping Air',
+ description: 'Sybil-resistant token airdrop powered by World ID and AgentKit',
+}
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+ {children}
+
+
+ )
+}
diff --git a/sybil-airdrop/src/app/page.tsx b/sybil-airdrop/src/app/page.tsx
new file mode 100644
index 0000000..1ac867c
--- /dev/null
+++ b/sybil-airdrop/src/app/page.tsx
@@ -0,0 +1,42 @@
+import CampaignInfo from '@/components/CampaignInfo'
+import ClaimForm from '@/components/ClaimForm'
+
+export default function ClaimPage() {
+ return (
+
+
+
Claim Airdrop
+
+ Prove your agent is backed by a real human and claim your tokens. One claim per human.
+
+
+
+
+
+
+
+
How it works
+
+
+
Step 1
+
+ Register your agent in the AgentBook with World ID.
+
+
+
+
Step 2
+
+ Sign a challenge to prove wallet ownership.
+
+
+
+
Step 3
+
+ Claim tokens. The server verifies and sends them.
+
+
+
+
+
+ )
+}
diff --git a/sybil-airdrop/src/app/register/page.tsx b/sybil-airdrop/src/app/register/page.tsx
new file mode 100644
index 0000000..5836a8a
--- /dev/null
+++ b/sybil-airdrop/src/app/register/page.tsx
@@ -0,0 +1,47 @@
+import RegisterForm from '@/components/RegisterForm'
+
+export default function RegisterPage() {
+ return (
+
+
+
Register Your Agent
+
+ Verify your identity with World ID and link your wallet to the AgentBook.
+ This is required before you can claim the airdrop.
+
+
+
+
+
+
+
What happens
+
+
+
+ 1
+
+
+ Connect your wallet to identify the agent address.
+
+
+
+
+ 2
+
+
+ Verify with World ID using your Orb-verified identity.
+
+
+
+
+ 3
+
+
+ Register on-chain. Your wallet is linked to your anonymous human ID.
+
+
+
+
+
+ )
+}
diff --git a/sybil-airdrop/src/app/status/page.tsx b/sybil-airdrop/src/app/status/page.tsx
new file mode 100644
index 0000000..2e4a27a
--- /dev/null
+++ b/sybil-airdrop/src/app/status/page.tsx
@@ -0,0 +1,16 @@
+import StatusCard from '@/components/StatusCard'
+
+export default function StatusPage() {
+ return (
+
+
+
Claim Status
+
+ Check if an agent is registered and whether the associated human has claimed.
+
+
+
+
+
+ )
+}
diff --git a/sybil-airdrop/src/components/CampaignInfo.tsx b/sybil-airdrop/src/components/CampaignInfo.tsx
new file mode 100644
index 0000000..a72dc6e
--- /dev/null
+++ b/sybil-airdrop/src/components/CampaignInfo.tsx
@@ -0,0 +1,21 @@
+export default function CampaignInfo() {
+ return (
+
+ )
+}
diff --git a/sybil-airdrop/src/components/ClaimForm.tsx b/sybil-airdrop/src/components/ClaimForm.tsx
new file mode 100644
index 0000000..724a542
--- /dev/null
+++ b/sybil-airdrop/src/components/ClaimForm.tsx
@@ -0,0 +1,221 @@
+'use client'
+
+import { useState } from 'react'
+import { createWalletClient, custom } from 'viem'
+import { base } from 'viem/chains'
+import { SiweMessage } from 'siwe'
+
+type ClaimState =
+ | { step: 'disconnected' }
+ | { step: 'idle'; address: string }
+ | { step: 'fetching-challenge'; address: string }
+ | { step: 'signing'; address: string }
+ | { step: 'submitting'; address: string }
+ | { step: 'success'; address: string; txHash: string; explorerUrl: string }
+ | { step: 'error'; address: string; message: string }
+
+export default function ClaimForm() {
+ const [state, setState] = useState({ step: 'disconnected' })
+
+ const connect = async () => {
+ const ethereum = (window as unknown as { ethereum?: { request: (...args: unknown[]) => Promise } }).ethereum
+ if (!ethereum) {
+ alert('No wallet found. Please install MetaMask or another wallet extension.')
+ return
+ }
+ const client = createWalletClient({ chain: base, transport: custom(ethereum) })
+ const [addr] = await client.requestAddresses()
+ setState({ step: 'idle', address: addr })
+ }
+
+ const claim = async () => {
+ if (state.step === 'disconnected') return
+ const { address } = state
+
+ try {
+ setState({ step: 'fetching-challenge', address })
+ const challengeRes = await fetch('/api/challenge')
+ if (!challengeRes.ok) throw new Error('Failed to fetch challenge')
+ const challenge = await challengeRes.json()
+
+ setState({ step: 'signing', address })
+ const ethereum = (window as unknown as { ethereum?: { request: (...args: unknown[]) => Promise } }).ethereum
+ const client = createWalletClient({ chain: base, transport: custom(ethereum!) })
+
+ const chainIdNum = parseInt(challenge.chainId.split(':')[1])
+ const message = new SiweMessage({
+ domain: challenge.domain,
+ address,
+ statement: 'Verify your agent is backed by a real human to claim the airdrop',
+ uri: challenge.uri,
+ version: '1',
+ chainId: chainIdNum,
+ nonce: challenge.nonce,
+ issuedAt: challenge.issuedAt,
+ expirationTime: challenge.expirationTime,
+ })
+
+ const signature = await client.signMessage({
+ account: address as `0x${string}`,
+ message: message.prepareMessage(),
+ })
+
+ const payloadData = {
+ domain: message.domain,
+ address: message.address,
+ statement: message.statement,
+ uri: message.uri,
+ version: message.version,
+ chainId: challenge.chainId,
+ nonce: message.nonce,
+ issuedAt: message.issuedAt,
+ expirationTime: message.expirationTime,
+ signature,
+ type: 'eip191' as const,
+ }
+
+ const payload = btoa(JSON.stringify(payloadData))
+
+ setState({ step: 'submitting', address })
+ const res = await fetch('/api/claim', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ payload }),
+ })
+
+ const data = await res.json()
+ if (!res.ok) throw new Error(data.error || 'Claim failed')
+
+ setState({
+ step: 'success',
+ address,
+ txHash: data.txHash,
+ explorerUrl: `https://basescan.org/tx/${data.txHash}`,
+ })
+ } catch (e) {
+ console.error(e)
+ setState({
+ step: 'error',
+ address,
+ message: (e as Error).message,
+ })
+ }
+ }
+
+ const isLoading =
+ state.step === 'fetching-challenge' || state.step === 'signing' || state.step === 'submitting'
+
+ return (
+
+
+
Prove & Claim
+
Sign a challenge to verify your agent is human-backed
+
+
+
+ {state.step !== 'disconnected' && (
+
+ )}
+
+ {state.step !== 'disconnected' && (
+
+ Connected
+
+ {state.address.slice(0, 6)}...{state.address.slice(-4)}
+
+
+ )}
+
+ {state.step === 'success' && (
+
+ )}
+
+ {state.step === 'error' && (
+
+
Failed
+
{state.message}
+
+ )}
+
+ {state.step === 'disconnected' ? (
+
+ Connect Wallet
+
+ ) : (
+
+ {isLoading && (
+
+
+
+
+ )}
+ {state.step === 'idle' && 'Prove Humanity & Claim'}
+ {state.step === 'fetching-challenge' && 'Fetching Challenge...'}
+ {state.step === 'signing' && 'Waiting for Signature...'}
+ {state.step === 'submitting' && 'Submitting...'}
+ {state.step === 'success' && 'Claimed'}
+ {state.step === 'error' && 'Try Again'}
+
+ )}
+
+ {state.step === 'disconnected' && (
+
+ Your agent must be registered first.
+
+ )}
+
+
+ )
+}
+
+function StepIndicator({ active, done, label }: { active: boolean; done: boolean; label: string }) {
+ return (
+
+ {label}
+
+ )
+}
diff --git a/sybil-airdrop/src/components/RegisterForm.tsx b/sybil-airdrop/src/components/RegisterForm.tsx
new file mode 100644
index 0000000..7716d5d
--- /dev/null
+++ b/sybil-airdrop/src/components/RegisterForm.tsx
@@ -0,0 +1,244 @@
+'use client'
+
+import { useState, useCallback } from 'react'
+import {
+ IDKitRequestWidget,
+ orbLegacy,
+ type IDKitResult,
+ type IDKitRequestWidgetProps,
+} from '@worldcoin/idkit'
+import type { RpContext, ResponseItemV3 } from '@worldcoin/idkit'
+import {
+ createWalletClient,
+ createPublicClient,
+ custom,
+ http,
+ decodeAbiParameters,
+} from 'viem'
+import { base } from 'viem/chains'
+import { AGENT_BOOK_ABI } from '@/lib/contracts'
+
+type RegState =
+ | { step: 'disconnected' }
+ | { step: 'idle'; address: string }
+ | { step: 'verifying'; address: string }
+ | { step: 'submitting'; address: string }
+ | { step: 'success'; address: string; txHash: string }
+ | { step: 'error'; address: string; message: string }
+
+const APP_ID = 'app_51925df0f1fa5388389b749e6a0f576c' as const
+const ACTION = 'claim-token-dropping-air'
+
+export default function RegisterForm() {
+ const [state, setState] = useState({ step: 'disconnected' })
+ const [nonce, setNonce] = useState(0n)
+ const [rpContext, setRpContext] = useState(null)
+ const [widgetOpen, setWidgetOpen] = useState(false)
+
+ const agentBookAddress = process.env.NEXT_PUBLIC_AGENTBOOK_ADDRESS as `0x${string}` | undefined
+
+ const getPublicClient = useCallback(() => {
+ return createPublicClient({
+ chain: base,
+ transport: http('https://mainnet.base.org'),
+ })
+ }, [])
+
+ const connect = async () => {
+ const ethereum = (window as unknown as { ethereum?: { request: (...args: unknown[]) => Promise } }).ethereum
+ if (!ethereum) {
+ alert('No wallet found. Install MetaMask or another wallet.')
+ return
+ }
+ const client = createWalletClient({ chain: base, transport: custom(ethereum) })
+ const [addr] = await client.requestAddresses()
+
+ if (agentBookAddress) {
+ const publicClient = getPublicClient()
+ const n = await publicClient.readContract({
+ address: agentBookAddress,
+ abi: AGENT_BOOK_ABI,
+ functionName: 'getNextNonce',
+ args: [addr],
+ })
+ setNonce(n)
+ }
+
+ const res = await fetch('/api/rp-context')
+ if (res.ok) {
+ const data = await res.json()
+ setRpContext(data.rp_context)
+ }
+
+ setState({ step: 'idle', address: addr })
+ }
+
+ const handleStartVerify = () => {
+ if (!rpContext) return
+ setWidgetOpen(true)
+ }
+
+ const handleSuccess = async (result: IDKitResult) => {
+ if (state.step === 'disconnected') return
+ const { address } = state
+ setWidgetOpen(false)
+
+ try {
+ setState({ step: 'submitting', address })
+
+ if (!agentBookAddress) {
+ throw new Error('AgentBook contract not configured')
+ }
+
+ const v3Response = result.responses?.find(
+ (r): r is ResponseItemV3 => 'merkle_root' in r
+ )
+
+ if (!v3Response) {
+ throw new Error('No v3 proof received. Orb verification required.')
+ }
+
+ const root = BigInt(v3Response.merkle_root)
+ const nullifierHash = BigInt(v3Response.nullifier)
+ const proof = decodeProofFromAbi(v3Response.proof)
+
+ const ethereum = (window as unknown as { ethereum?: { request: (...args: unknown[]) => Promise } }).ethereum
+ const walletClient = createWalletClient({ chain: base, transport: custom(ethereum!) })
+ const publicClient = getPublicClient()
+
+ const { request } = await publicClient.simulateContract({
+ address: agentBookAddress,
+ abi: AGENT_BOOK_ABI,
+ functionName: 'register',
+ args: [address as `0x${string}`, root, nonce, nullifierHash, proof],
+ account: address as `0x${string}`,
+ })
+
+ const txHash = await walletClient.writeContract(request)
+ setState({ step: 'success', address, txHash })
+ } catch (e) {
+ console.error(e)
+ setState({
+ step: 'error',
+ address,
+ message: (e as Error).message,
+ })
+ }
+ }
+
+ const isLoading = state.step === 'verifying' || state.step === 'submitting'
+
+ return (
+
+
+
Register Agent
+
+ Link your wallet to a World ID-verified human identity
+
+
+
+
+ {state.step !== 'disconnected' && (
+
+ Wallet
+
+ {state.address.slice(0, 6)}...{state.address.slice(-4)}
+
+
+ )}
+
+ {state.step === 'success' && (
+
+ )}
+
+ {state.step === 'error' && (
+
+
Registration failed
+
{state.message}
+
+ )}
+
+ {state.step === 'disconnected' ? (
+
+ Connect Wallet
+
+ ) : state.step === 'success' ? (
+
+ Go to Claim Page
+
+ ) : (
+ <>
+
+ {isLoading && (
+
+
+
+
+ )}
+ {state.step === 'idle' && 'Verify with World ID'}
+ {state.step === 'verifying' && 'Verifying...'}
+ {state.step === 'submitting' && 'Submitting Transaction...'}
+ {state.step === 'error' && 'Try Again'}
+
+
+ {rpContext && (
+
+ )}
+ >
+ )}
+
+ {state.step === 'disconnected' && (
+
+ You need a World ID Orb verification to register.
+
+ )}
+
+
+ )
+}
+
+function decodeProofFromAbi(proof: string): readonly [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint] {
+ const decoded = decodeAbiParameters(
+ Array.from({ length: 8 }, () => ({ type: 'uint256' as const })),
+ proof as `0x${string}`
+ )
+ return decoded as unknown as readonly [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]
+}
diff --git a/sybil-airdrop/src/components/StatusCard.tsx b/sybil-airdrop/src/components/StatusCard.tsx
new file mode 100644
index 0000000..62d19ac
--- /dev/null
+++ b/sybil-airdrop/src/components/StatusCard.tsx
@@ -0,0 +1,153 @@
+'use client'
+
+import { useState } from 'react'
+
+interface ClaimInfo {
+ txHash: string
+ amount: string
+ claimedAt: string
+ explorerUrl: string
+}
+
+interface StatusResult {
+ registered: boolean
+ humanId: string | null
+ claimed: boolean
+ claim: ClaimInfo | null
+}
+
+export default function StatusCard() {
+ const [address, setAddress] = useState('')
+ const [status, setStatus] = useState(null)
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState(null)
+
+ const handleCheck = async () => {
+ if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address)) {
+ setError('Please enter a valid Ethereum address')
+ return
+ }
+
+ setLoading(true)
+ setError(null)
+ setStatus(null)
+
+ try {
+ const res = await fetch(`/api/status?address=${address}`)
+ const data = await res.json()
+ if (!res.ok) throw new Error(data.error ?? 'Failed to check status')
+ setStatus(data)
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to check status')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ return (
+
+
+
Check Status
+
Look up registration and claim status by address
+
+
+
+
+
setAddress(e.target.value)}
+ placeholder="0x..."
+ className="flex-1 px-3 py-2 rounded-lg bg-white border border-border
+ text-text-primary placeholder:text-text-muted font-mono text-sm
+ focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50
+ transition-colors"
+ />
+
+ {loading ? (
+
+
+
+
+ ) : (
+ 'Check'
+ )}
+
+
+
+ {error && (
+
+ )}
+
+ {status && (
+
+
+ Registered
+
+
+
+ {status.humanId && (
+
+ Human ID
+ {status.humanId}
+
+ )}
+
+
+ Claimed
+
+
+
+ {status.claim && (
+ <>
+
+
+
+ Date
+
+ {new Date(status.claim.claimedAt).toLocaleDateString(undefined, {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ })}
+
+
+ >
+ )}
+
+ )}
+
+
+ )
+}
+
+function Badge({ active, activeLabel, inactiveLabel }: { active: boolean; activeLabel: string; inactiveLabel: string }) {
+ return (
+
+ {active ? activeLabel : inactiveLabel}
+
+ )
+}
diff --git a/sybil-airdrop/src/lib/agentkit.ts b/sybil-airdrop/src/lib/agentkit.ts
new file mode 100644
index 0000000..770faf6
--- /dev/null
+++ b/sybil-airdrop/src/lib/agentkit.ts
@@ -0,0 +1,32 @@
+import {
+ parseAgentkitHeader,
+ validateAgentkitMessage,
+ verifyAgentkitSignature,
+ createAgentBookVerifier,
+ type AgentBookVerifier,
+} from '@worldcoin/agentkit'
+import { env } from './config'
+
+let _agentBook: AgentBookVerifier | null = null
+
+export function getAgentBook(): AgentBookVerifier {
+ if (_agentBook) return _agentBook
+ _agentBook = createAgentBookVerifier({
+ contractAddress: env.AGENTBOOK_CONTRACT_ADDRESS as `0x${string}`,
+ rpcUrl: env.AGENTBOOK_RPC_URL,
+ })
+ return _agentBook
+}
+
+export const agentBook = new Proxy({} as AgentBookVerifier, {
+ get(_, prop: string) {
+ const instance = getAgentBook()
+ const value = instance[prop as keyof AgentBookVerifier]
+ if (typeof value === 'function') {
+ return value.bind(instance)
+ }
+ return value
+ },
+})
+
+export { parseAgentkitHeader, validateAgentkitMessage, verifyAgentkitSignature }
diff --git a/sybil-airdrop/src/lib/airdrop.ts b/sybil-airdrop/src/lib/airdrop.ts
new file mode 100644
index 0000000..4b56174
--- /dev/null
+++ b/sybil-airdrop/src/lib/airdrop.ts
@@ -0,0 +1,65 @@
+import { createWalletClient, http, publicActions, parseAbi } from 'viem'
+import { privateKeyToAccount } from 'viem/accounts'
+import { base } from 'viem/chains'
+import { env } from './config'
+
+const SYBIL_AIRDROP_ABI = parseAbi([
+ 'function claim(address agent, uint256 nullifierHash) external',
+ 'function hasClaimed(uint256 nullifierHash) view returns (bool)',
+ 'function remainingBalance() view returns (uint256)',
+ 'function totalClaims() view returns (uint256)',
+ 'function claimAmount() view returns (uint256)',
+ 'function token() view returns (address)',
+])
+
+function getWalletClient() {
+ const account = privateKeyToAccount(env.AIRDROP_SIGNER_PRIVATE_KEY as `0x${string}`)
+ return createWalletClient({
+ account,
+ chain: base,
+ transport: http(env.AGENTBOOK_RPC_URL),
+ }).extend(publicActions)
+}
+
+export async function executeAirdrop(
+ agentAddress: `0x${string}`,
+ nullifierHash: bigint
+): Promise<`0x${string}`> {
+ const client = getWalletClient()
+ const { request } = await client.simulateContract({
+ address: env.AIRDROP_CONTRACT_ADDRESS as `0x${string}`,
+ abi: SYBIL_AIRDROP_ABI,
+ functionName: 'claim',
+ args: [agentAddress, nullifierHash],
+ })
+ return client.writeContract(request)
+}
+
+export async function hasClaimedOnChain(nullifierHash: bigint): Promise {
+ const client = getWalletClient()
+ return client.readContract({
+ address: env.AIRDROP_CONTRACT_ADDRESS as `0x${string}`,
+ abi: SYBIL_AIRDROP_ABI,
+ functionName: 'hasClaimed',
+ args: [nullifierHash],
+ })
+}
+
+export async function getAirdropInfo() {
+ const client = getWalletClient()
+ const contractAddress = env.AIRDROP_CONTRACT_ADDRESS as `0x${string}`
+
+ const [remainingBalance, totalClaims, claimAmount, tokenAddress] = await Promise.all([
+ client.readContract({ address: contractAddress, abi: SYBIL_AIRDROP_ABI, functionName: 'remainingBalance' }),
+ client.readContract({ address: contractAddress, abi: SYBIL_AIRDROP_ABI, functionName: 'totalClaims' }),
+ client.readContract({ address: contractAddress, abi: SYBIL_AIRDROP_ABI, functionName: 'claimAmount' }),
+ client.readContract({ address: contractAddress, abi: SYBIL_AIRDROP_ABI, functionName: 'token' }),
+ ])
+
+ return {
+ remainingBalance: remainingBalance.toString(),
+ totalClaims: Number(totalClaims),
+ claimAmount: claimAmount.toString(),
+ tokenAddress,
+ }
+}
diff --git a/sybil-airdrop/src/lib/config.ts b/sybil-airdrop/src/lib/config.ts
new file mode 100644
index 0000000..3b9cf22
--- /dev/null
+++ b/sybil-airdrop/src/lib/config.ts
@@ -0,0 +1,31 @@
+import { z } from 'zod'
+
+const envSchema = z.object({
+ AGENTBOOK_CONTRACT_ADDRESS: z.string().startsWith('0x'),
+ AGENTBOOK_RPC_URL: z.string(),
+ AIRDROP_CONTRACT_ADDRESS: z.string().startsWith('0x'),
+ AIRDROP_SIGNER_PRIVATE_KEY: z.string().startsWith('0x'),
+ TOKEN_ADDRESS: z.string().startsWith('0x'),
+ CLAIM_AMOUNT: z.string(),
+ NEXT_PUBLIC_CHAIN_ID: z.string(),
+ NEXT_PUBLIC_DOMAIN: z.string(),
+})
+
+let _env: z.infer | null = null
+
+export function getEnv() {
+ if (_env) return _env
+ const result = envSchema.safeParse(process.env)
+ if (!result.success) {
+ console.error('Invalid environment variables:', result.error.format())
+ throw new Error('Invalid environment variables')
+ }
+ _env = result.data
+ return _env
+}
+
+export const env = new Proxy({} as z.infer, {
+ get(_, prop: string) {
+ return getEnv()[prop as keyof z.infer]
+ },
+})
diff --git a/sybil-airdrop/src/lib/contracts.ts b/sybil-airdrop/src/lib/contracts.ts
new file mode 100644
index 0000000..5a7f7ae
--- /dev/null
+++ b/sybil-airdrop/src/lib/contracts.ts
@@ -0,0 +1,7 @@
+import { parseAbi } from 'viem'
+
+export const AGENT_BOOK_ABI = parseAbi([
+ 'function register(address agent, uint256 root, uint256 nonce, uint256 nullifierHash, uint256[8] proof) external',
+ 'function lookupHuman(address) view returns (uint256)',
+ 'function getNextNonce(address) view returns (uint256)',
+])
diff --git a/sybil-airdrop/src/lib/db.ts b/sybil-airdrop/src/lib/db.ts
new file mode 100644
index 0000000..d42ab89
--- /dev/null
+++ b/sybil-airdrop/src/lib/db.ts
@@ -0,0 +1,28 @@
+import { createClient } from '@supabase/supabase-js'
+
+const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL!
+const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+
+const db = createClient(supabaseUrl, supabaseKey)
+
+export default db
+
+export type Claim = {
+ id: string
+ nullifierHash: string
+ agentAddress: string
+ txHash: string
+ amount: string
+ claimedAt: string
+}
+
+export type Challenge = {
+ id: string
+ nonce: string
+ domain: string
+ uri: string
+ chainId: string
+ createdAt: string
+ expiresAt: string
+ used: boolean
+}
diff --git a/sybil-airdrop/tailwind.config.ts b/sybil-airdrop/tailwind.config.ts
new file mode 100644
index 0000000..a9afa33
--- /dev/null
+++ b/sybil-airdrop/tailwind.config.ts
@@ -0,0 +1,49 @@
+import type { Config } from 'tailwindcss'
+
+const config: Config = {
+ content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
+ mono: ['JetBrains Mono', 'ui-monospace', 'monospace'],
+ },
+ colors: {
+ primary: {
+ DEFAULT: '#3B82F6',
+ dark: '#2563EB',
+ light: '#60A5FA',
+ 50: '#EFF6FF',
+ 100: '#DBEAFE',
+ },
+ surface: {
+ DEFAULT: '#FFFFFF',
+ raised: '#F9FAFB',
+ overlay: '#F3F4F6',
+ },
+ border: {
+ DEFAULT: '#E5E7EB',
+ light: '#F3F4F6',
+ },
+ 'text-primary': '#111827',
+ 'text-secondary': '#6B7280',
+ 'text-muted': '#9CA3AF',
+ success: {
+ DEFAULT: '#22C55E',
+ bg: '#F0FDF4',
+ },
+ error: {
+ DEFAULT: '#EF4444',
+ bg: '#FEF2F2',
+ },
+ warning: {
+ DEFAULT: '#F59E0B',
+ bg: '#FFFBEB',
+ },
+ },
+ },
+ },
+ plugins: [],
+}
+
+export default config
diff --git a/sybil-airdrop/tests/pages.spec.ts b/sybil-airdrop/tests/pages.spec.ts
new file mode 100644
index 0000000..087ec24
--- /dev/null
+++ b/sybil-airdrop/tests/pages.spec.ts
@@ -0,0 +1,1197 @@
+import { test, expect } from '@playwright/test'
+
+// ---------------------------------------------------------------------------
+// 1. NAVIGATION & LAYOUT
+// ---------------------------------------------------------------------------
+test.describe('Navigation & Layout', () => {
+ test('home page loads with correct title and heading', async ({ page }) => {
+ await page.goto('/')
+ await expect(page).toHaveTitle('Dropping Air')
+ await expect(page.locator('h1')).toHaveText('Claim Airdrop')
+ })
+
+ test('register page loads with heading', async ({ page }) => {
+ await page.goto('/register')
+ await expect(page.locator('h1')).toHaveText('Register Your Agent')
+ await expect(page.getByText('Connect Wallet')).toBeVisible()
+ })
+
+ test('status page loads with heading', async ({ page }) => {
+ await page.goto('/status')
+ await expect(page.locator('h1')).toHaveText('Claim Status')
+ await expect(page.getByPlaceholder('0x...')).toBeVisible()
+ })
+
+ test('navbar contains all links and network badge', async ({ page }) => {
+ await page.goto('/')
+ const nav = page.getByRole('navigation')
+
+ await expect(nav.getByRole('link', { name: 'Dropping Air' })).toBeVisible()
+ await expect(nav.getByRole('link', { name: 'Claim' })).toBeVisible()
+ await expect(nav.getByRole('link', { name: 'Register' })).toBeVisible()
+ await expect(nav.getByRole('link', { name: 'Status' })).toBeVisible()
+ await expect(nav.getByText('Base')).toBeVisible()
+ })
+
+ test('nav links navigate correctly between all pages', async ({ page }) => {
+ await page.goto('/')
+ const nav = page.getByRole('navigation')
+
+ await nav.getByRole('link', { name: 'Register' }).click()
+ await expect(page).toHaveURL('/register')
+ await expect(page.locator('h1')).toHaveText('Register Your Agent')
+
+ await nav.getByRole('link', { name: 'Status' }).click()
+ await expect(page).toHaveURL('/status')
+ await expect(page.locator('h1')).toHaveText('Claim Status')
+
+ await nav.getByRole('link', { name: 'Claim' }).click()
+ await expect(page).toHaveURL('/')
+ await expect(page.locator('h1')).toHaveText('Claim Airdrop')
+ })
+
+ test('logo link navigates to home', async ({ page }) => {
+ await page.goto('/register')
+ await page.getByRole('navigation').getByRole('link', { name: 'Dropping Air' }).click()
+ await expect(page).toHaveURL('/')
+ })
+
+ test('navbar is sticky at top of page', async ({ page }) => {
+ await page.goto('/')
+ const nav = page.getByRole('navigation')
+ await expect(nav).toHaveCSS('position', 'sticky')
+ })
+
+ test('non-existent route shows 404', async ({ page }) => {
+ const res = await page.goto('/nonexistent-page')
+ expect(res?.status()).toBe(404)
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 2. CAMPAIGN INFO (Home Page)
+// ---------------------------------------------------------------------------
+test.describe('Campaign Info', () => {
+ test('shows campaign details on home page', async ({ page }) => {
+ await page.goto('/')
+ const main = page.getByRole('main')
+ await expect(main.getByText('1.0 DROP')).toBeVisible()
+ await expect(main.getByText('Base')).toBeVisible()
+ await expect(main.getByText('1 / human')).toBeVisible()
+ })
+
+ test('shows campaign section heading', async ({ page }) => {
+ await page.goto('/')
+ await expect(page.getByRole('heading', { name: 'Campaign' })).toBeVisible()
+ })
+
+ test('shows per-claim, network, and limit labels', async ({ page }) => {
+ await page.goto('/')
+ await expect(page.getByText('Per claim')).toBeVisible()
+ await expect(page.getByText('Network')).toBeVisible()
+ await expect(page.getByText('Limit')).toBeVisible()
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 3. HOW IT WORKS (Home Page)
+// ---------------------------------------------------------------------------
+test.describe('How It Works', () => {
+ test('shows all three steps on claim page', async ({ page }) => {
+ await page.goto('/')
+ await expect(page.getByRole('heading', { name: 'How it works' })).toBeVisible()
+ await expect(page.getByText('Step 1')).toBeVisible()
+ await expect(page.getByText('Step 2')).toBeVisible()
+ await expect(page.getByText('Step 3')).toBeVisible()
+ })
+
+ test('step descriptions reference Register, Sign, Claim', async ({ page }) => {
+ await page.goto('/')
+ const main = page.getByRole('main')
+ await expect(main.getByText('Register your agent in the AgentBook with World ID.')).toBeVisible()
+ await expect(main.getByText('Sign a challenge to prove wallet ownership.')).toBeVisible()
+ await expect(main.getByText('Claim tokens. The server verifies and sends them.')).toBeVisible()
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 4. CLAIM FORM — Disconnected State
+// ---------------------------------------------------------------------------
+test.describe('Claim Form — Disconnected', () => {
+ test('shows Connect Wallet button initially', async ({ page }) => {
+ await page.goto('/')
+ const btn = page.getByRole('button', { name: 'Connect Wallet' })
+ await expect(btn).toBeVisible()
+ await expect(btn).toBeEnabled()
+ })
+
+ test('does NOT show step indicators when disconnected', async ({ page }) => {
+ await page.goto('/')
+ await expect(page.getByText('Challenge')).not.toBeVisible()
+ await expect(page.getByText('Sign', { exact: true })).not.toBeVisible()
+ })
+
+ test('shows registration link when disconnected', async ({ page }) => {
+ await page.goto('/')
+ const link = page.getByRole('link', { name: 'registered' })
+ await expect(link).toBeVisible()
+ await expect(link).toHaveAttribute('href', '/register')
+ })
+
+ test('registration link navigates to /register', async ({ page }) => {
+ await page.goto('/')
+ await page.getByRole('link', { name: 'registered' }).click()
+ await expect(page).toHaveURL('/register')
+ })
+
+ test('Prove & Claim section has proper heading and description', async ({ page }) => {
+ await page.goto('/')
+ await expect(page.getByRole('heading', { name: 'Prove & Claim' })).toBeVisible()
+ await expect(page.getByText('Sign a challenge to verify your agent is human-backed')).toBeVisible()
+ })
+
+ test('shows alert for missing wallet when no ethereum provider', async ({ page }) => {
+ page.on('dialog', async dialog => {
+ expect(dialog.message()).toContain('No wallet found')
+ await dialog.accept()
+ })
+ await page.goto('/')
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 5. REGISTER FORM — Disconnected State
+// ---------------------------------------------------------------------------
+test.describe('Register Form — Disconnected', () => {
+ test('shows Connect Wallet button initially', async ({ page }) => {
+ await page.goto('/register')
+ const btn = page.getByRole('button', { name: 'Connect Wallet' })
+ await expect(btn).toBeVisible()
+ await expect(btn).toBeEnabled()
+ })
+
+ test('shows Orb verification message', async ({ page }) => {
+ await page.goto('/register')
+ await expect(page.getByText('You need a World ID Orb verification to register.')).toBeVisible()
+ })
+
+ test('shows What happens section with 3 steps', async ({ page }) => {
+ await page.goto('/register')
+ await expect(page.getByRole('heading', { name: 'What happens' })).toBeVisible()
+ await expect(page.getByText('Connect your wallet to identify the agent address.')).toBeVisible()
+ await expect(page.getByText('Verify with World ID using your Orb-verified identity.')).toBeVisible()
+ await expect(
+ page.getByText('Register on-chain. Your wallet is linked to your anonymous human ID.')
+ ).toBeVisible()
+ })
+
+ test('shows alert for missing wallet when no ethereum provider', async ({ page }) => {
+ page.on('dialog', async dialog => {
+ expect(dialog.message()).toContain('No wallet found')
+ await dialog.accept()
+ })
+ await page.goto('/register')
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 6. STATUS LOOKUP — Happy Path
+// ---------------------------------------------------------------------------
+test.describe('Status Lookup — Happy Path', () => {
+ test('checking unregistered address shows Registered: No, Claimed: No', async ({ page }) => {
+ await page.goto('/status')
+ await page.getByPlaceholder('0x...').fill('0x0000000000000000000000000000000000000000')
+ await page.getByRole('button', { name: 'Check' }).click()
+
+ await expect(page.getByText('Registered').or(page.getByText('Failed to check status'))).toBeVisible({
+ timeout: 10000,
+ })
+
+ const registered = page.locator('text=Registered').first()
+ if (await registered.isVisible()) {
+ await expect(page.getByText('No').first()).toBeVisible()
+ }
+ })
+
+ test('Check button shows loading spinner while fetching', async ({ page }) => {
+ await page.goto('/status')
+ await page.getByPlaceholder('0x...').fill('0x0000000000000000000000000000000000000001')
+
+ await page.route('**/api/status*', async route => {
+ await new Promise(r => setTimeout(r, 2000))
+ await route.continue()
+ })
+
+ await page.getByRole('button', { name: 'Check' }).click()
+ const btn = page.getByRole('button').filter({ has: page.locator('svg.animate-spin') })
+ await expect(btn).toBeVisible()
+ })
+
+ test('can check multiple addresses sequentially', async ({ page }) => {
+ await page.goto('/status')
+
+ await page.getByPlaceholder('0x...').fill('0x0000000000000000000000000000000000000000')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Registered').or(page.getByText('Failed'))).toBeVisible({ timeout: 10000 })
+
+ await page.getByPlaceholder('0x...').fill('0x0000000000000000000000000000000000000001')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Registered').or(page.getByText('Failed'))).toBeVisible({ timeout: 10000 })
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 7. STATUS LOOKUP — Unhappy Paths
+// ---------------------------------------------------------------------------
+test.describe('Status Lookup — Validation & Errors', () => {
+ test('rejects empty input', async ({ page }) => {
+ await page.goto('/status')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Please enter a valid Ethereum address')).toBeVisible()
+ })
+
+ test('rejects plain text', async ({ page }) => {
+ await page.goto('/status')
+ await page.getByPlaceholder('0x...').fill('hello-world')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Please enter a valid Ethereum address')).toBeVisible()
+ })
+
+ test('rejects address missing 0x prefix', async ({ page }) => {
+ await page.goto('/status')
+ await page.getByPlaceholder('0x...').fill('0000000000000000000000000000000000000000')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Please enter a valid Ethereum address')).toBeVisible()
+ })
+
+ test('rejects address that is too short', async ({ page }) => {
+ await page.goto('/status')
+ await page.getByPlaceholder('0x...').fill('0x1234')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Please enter a valid Ethereum address')).toBeVisible()
+ })
+
+ test('rejects address that is too long', async ({ page }) => {
+ await page.goto('/status')
+ await page.getByPlaceholder('0x...').fill('0x' + 'a'.repeat(50))
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Please enter a valid Ethereum address')).toBeVisible()
+ })
+
+ test('rejects address with non-hex characters', async ({ page }) => {
+ await page.goto('/status')
+ await page.getByPlaceholder('0x...').fill('0xGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Please enter a valid Ethereum address')).toBeVisible()
+ })
+
+ test('handles API error gracefully', async ({ page }) => {
+ await page.goto('/status')
+ await page.route('**/api/status*', route =>
+ route.fulfill({ status: 500, contentType: 'application/json', body: JSON.stringify({ error: 'Server error' }) })
+ )
+ await page.getByPlaceholder('0x...').fill('0x0000000000000000000000000000000000000000')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Server error').or(page.getByText('Failed to check status'))).toBeVisible({
+ timeout: 5000,
+ })
+ })
+
+ test('handles network timeout gracefully', async ({ page }) => {
+ await page.goto('/status')
+ await page.route('**/api/status*', route => route.abort('timedout'))
+ await page.getByPlaceholder('0x...').fill('0x0000000000000000000000000000000000000000')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Failed to check status').or(page.getByText('fetch'))).toBeVisible({
+ timeout: 5000,
+ })
+ })
+
+ test('clears previous error when submitting new lookup', async ({ page }) => {
+ await page.goto('/status')
+ await page.getByPlaceholder('0x...').fill('invalid')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Please enter a valid Ethereum address')).toBeVisible()
+
+ await page.getByPlaceholder('0x...').fill('0x0000000000000000000000000000000000000000')
+ await page.getByRole('button', { name: 'Check' }).click()
+ await expect(page.getByText('Please enter a valid Ethereum address')).not.toBeVisible()
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 8. API — GET /api/challenge
+// ---------------------------------------------------------------------------
+test.describe('API — GET /api/challenge', () => {
+ test('returns 200 with all required fields', async ({ request }) => {
+ const res = await request.get('/api/challenge')
+ expect(res.status()).toBe(200)
+
+ const data = await res.json()
+ expect(data).toHaveProperty('nonce')
+ expect(data).toHaveProperty('domain')
+ expect(data).toHaveProperty('uri')
+ expect(data).toHaveProperty('chainId')
+ expect(data).toHaveProperty('issuedAt')
+ expect(data).toHaveProperty('expirationTime')
+ expect(data).toHaveProperty('supportedChains')
+ })
+
+ test('returns correct chainId', async ({ request }) => {
+ const res = await request.get('/api/challenge')
+ const data = await res.json()
+ expect(data.chainId).toBe('eip155:8453')
+ })
+
+ test('supportedChains contains the chainId', async ({ request }) => {
+ const res = await request.get('/api/challenge')
+ const data = await res.json()
+ expect(data.supportedChains).toContain(data.chainId)
+ })
+
+ test('nonce is a non-empty string', async ({ request }) => {
+ const res = await request.get('/api/challenge')
+ const data = await res.json()
+ expect(typeof data.nonce).toBe('string')
+ expect(data.nonce.length).toBeGreaterThan(0)
+ })
+
+ test('successive calls return different nonces', async ({ request }) => {
+ const res1 = await request.get('/api/challenge')
+ const res2 = await request.get('/api/challenge')
+ const data1 = await res1.json()
+ const data2 = await res2.json()
+ expect(data1.nonce).not.toBe(data2.nonce)
+ })
+
+ test('issuedAt and expirationTime are valid ISO dates', async ({ request }) => {
+ const res = await request.get('/api/challenge')
+ const data = await res.json()
+ expect(new Date(data.issuedAt).toISOString()).toBe(data.issuedAt)
+ expect(new Date(data.expirationTime).toISOString()).toBe(data.expirationTime)
+ })
+
+ test('challenge expires 5 minutes after issuance', async ({ request }) => {
+ const res = await request.get('/api/challenge')
+ const data = await res.json()
+ const issued = new Date(data.issuedAt).getTime()
+ const expires = new Date(data.expirationTime).getTime()
+ expect(expires - issued).toBe(5 * 60 * 1000)
+ })
+
+ test('uri is a well-formed URL using the domain', async ({ request }) => {
+ const res = await request.get('/api/challenge')
+ const data = await res.json()
+ expect(data.uri).toContain(data.domain)
+ expect(() => new URL(data.uri)).not.toThrow()
+ })
+
+ test('rejects POST method', async ({ request }) => {
+ const res = await request.post('/api/challenge')
+ expect(res.status()).toBe(405)
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 9. API — POST /api/claim
+// ---------------------------------------------------------------------------
+test.describe('API — POST /api/claim', () => {
+ test('rejects empty body', async ({ request }) => {
+ const res = await request.post('/api/claim', { data: {} })
+ expect(res.status()).toBe(400)
+ const data = await res.json()
+ expect(data.error).toContain('Missing or invalid payload')
+ })
+
+ test('rejects null payload', async ({ request }) => {
+ const res = await request.post('/api/claim', { data: { payload: null } })
+ expect(res.status()).toBe(400)
+ const data = await res.json()
+ expect(data.error).toContain('Missing or invalid payload')
+ })
+
+ test('rejects numeric payload', async ({ request }) => {
+ const res = await request.post('/api/claim', { data: { payload: 12345 } })
+ expect(res.status()).toBe(400)
+ })
+
+ test('rejects non-base64 payload string', async ({ request }) => {
+ const res = await request.post('/api/claim', { data: { payload: 'not-valid-base64!!!' } })
+ expect(res.status()).toBe(400)
+ })
+
+ test('rejects valid base64 but invalid JSON inside', async ({ request }) => {
+ const encoded = Buffer.from('this is not json').toString('base64')
+ const res = await request.post('/api/claim', { data: { payload: encoded } })
+ expect(res.status()).toBe(400)
+ })
+
+ test('rejects valid base64+JSON but missing required fields', async ({ request }) => {
+ const encoded = Buffer.from(JSON.stringify({ foo: 'bar' })).toString('base64')
+ const res = await request.post('/api/claim', { data: { payload: encoded } })
+ expect(res.status()).toBe(400)
+ })
+
+ test('rejects payload with invalid signature type', async ({ request }) => {
+ const payload = {
+ domain: 'localhost',
+ address: '0x0000000000000000000000000000000000000000',
+ uri: 'http://localhost',
+ version: '1',
+ chainId: 'eip155:8453',
+ type: 'invalid_type',
+ nonce: 'testnonce',
+ issuedAt: new Date().toISOString(),
+ signature: '0xfake',
+ }
+ const encoded = Buffer.from(JSON.stringify(payload)).toString('base64')
+ const res = await request.post('/api/claim', { data: { payload: encoded } })
+ expect(res.status()).toBe(400)
+ })
+
+ test('rejects payload with expired challenge nonce', async ({ request }) => {
+ const payload = {
+ domain: 'sybil-airdrop.vercel.app',
+ address: '0x0000000000000000000000000000000000000000',
+ uri: 'https://sybil-airdrop.vercel.app',
+ version: '1',
+ chainId: 'eip155:8453',
+ type: 'eip191',
+ nonce: 'expired-nonce-that-does-not-exist',
+ issuedAt: new Date().toISOString(),
+ signature: '0x' + 'ab'.repeat(65),
+ }
+ const encoded = Buffer.from(JSON.stringify(payload)).toString('base64')
+ const res = await request.post('/api/claim', { data: { payload: encoded } })
+ expect([400, 401]).toContain(res.status())
+ })
+
+ test('rejects GET method', async ({ request }) => {
+ const res = await request.get('/api/claim')
+ expect(res.status()).toBe(405)
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 10. API — GET /api/status
+// ---------------------------------------------------------------------------
+test.describe('API — GET /api/status', () => {
+ test('rejects missing address parameter', async ({ request }) => {
+ const res = await request.get('/api/status')
+ expect(res.status()).toBe(400)
+ const data = await res.json()
+ expect(data.error).toContain('Invalid')
+ })
+
+ test('rejects empty address parameter', async ({ request }) => {
+ const res = await request.get('/api/status?address=')
+ expect(res.status()).toBe(400)
+ })
+
+ test('rejects invalid address format', async ({ request }) => {
+ const res = await request.get('/api/status?address=invalid')
+ expect(res.status()).toBe(400)
+ const data = await res.json()
+ expect(data.error).toContain('Invalid')
+ })
+
+ test('rejects address missing 0x prefix', async ({ request }) => {
+ const res = await request.get('/api/status?address=0000000000000000000000000000000000000000')
+ expect(res.status()).toBe(400)
+ })
+
+ test('rejects too-short address', async ({ request }) => {
+ const res = await request.get('/api/status?address=0x1234')
+ expect(res.status()).toBe(400)
+ })
+
+ test('rejects too-long address', async ({ request }) => {
+ const res = await request.get('/api/status?address=0x' + 'a'.repeat(50))
+ expect(res.status()).toBe(400)
+ })
+
+ test('returns status for valid unregistered address', async ({ request }) => {
+ const res = await request.get('/api/status?address=0x0000000000000000000000000000000000000000')
+ expect(res.status()).toBe(200)
+ const data = await res.json()
+ expect(data).toHaveProperty('registered')
+ expect(data).toHaveProperty('claimed')
+ expect(typeof data.registered).toBe('boolean')
+ expect(typeof data.claimed).toBe('boolean')
+ })
+
+ test('unregistered address returns registered=false', async ({ request }) => {
+ const res = await request.get('/api/status?address=0x0000000000000000000000000000000000000000')
+ const data = await res.json()
+ expect(data.registered).toBe(false)
+ expect(data.humanId).toBeNull()
+ expect(data.claimed).toBe(false)
+ expect(data.claim).toBeNull()
+ })
+
+ test('accepts checksummed address', async ({ request }) => {
+ const res = await request.get('/api/status?address=0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B')
+ expect(res.status()).toBe(200)
+ })
+
+ test('accepts lowercase address', async ({ request }) => {
+ const res = await request.get('/api/status?address=0xab5801a7d398351b8be11c439e05c5b3259aec9b')
+ expect(res.status()).toBe(200)
+ })
+
+ test('rejects POST method', async ({ request }) => {
+ const res = await request.post('/api/status')
+ expect(res.status()).toBe(405)
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 11. API — GET /api/rp-context
+// ---------------------------------------------------------------------------
+test.describe('API — GET /api/rp-context', () => {
+ test('returns 200 with rp_context object', async ({ request }) => {
+ const res = await request.get('/api/rp-context')
+ expect(res.status()).toBe(200)
+
+ const data = await res.json()
+ expect(data).toHaveProperty('rp_context')
+ })
+
+ test('rp_context has all required fields', async ({ request }) => {
+ const res = await request.get('/api/rp-context')
+ const { rp_context } = await res.json()
+
+ expect(rp_context).toHaveProperty('rp_id')
+ expect(rp_context).toHaveProperty('nonce')
+ expect(rp_context).toHaveProperty('created_at')
+ expect(rp_context).toHaveProperty('expires_at')
+ expect(rp_context).toHaveProperty('signature')
+ })
+
+ test('rp_id matches expected format', async ({ request }) => {
+ const res = await request.get('/api/rp-context')
+ const { rp_context } = await res.json()
+ expect(rp_context.rp_id).toMatch(/^rp_[a-f0-9]+$/)
+ })
+
+ test('nonce is a hex string', async ({ request }) => {
+ const res = await request.get('/api/rp-context')
+ const { rp_context } = await res.json()
+ expect(rp_context.nonce).toMatch(/^0x[a-f0-9]+$/)
+ })
+
+ test('signature is a hex string', async ({ request }) => {
+ const res = await request.get('/api/rp-context')
+ const { rp_context } = await res.json()
+ expect(rp_context.signature).toMatch(/^0x[a-f0-9]+$/)
+ })
+
+ test('expires_at is after created_at', async ({ request }) => {
+ const res = await request.get('/api/rp-context')
+ const { rp_context } = await res.json()
+ expect(rp_context.expires_at).toBeGreaterThan(rp_context.created_at)
+ })
+
+ test('context expires ~5 minutes after creation', async ({ request }) => {
+ const res = await request.get('/api/rp-context')
+ const { rp_context } = await res.json()
+ const diff = rp_context.expires_at - rp_context.created_at
+ expect(diff).toBe(300)
+ })
+
+ test('successive calls return different nonces', async ({ request }) => {
+ const res1 = await request.get('/api/rp-context')
+ const res2 = await request.get('/api/rp-context')
+ const ctx1 = (await res1.json()).rp_context
+ const ctx2 = (await res2.json()).rp_context
+ expect(ctx1.nonce).not.toBe(ctx2.nonce)
+ })
+
+ test('rejects POST method', async ({ request }) => {
+ const res = await request.post('/api/rp-context')
+ expect(res.status()).toBe(405)
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 12. CLAIM FORM — Wallet Connection (mocked provider)
+// ---------------------------------------------------------------------------
+test.describe('Claim Form — Wallet Interaction', () => {
+ const MOCK_ADDRESS = '0x1234567890abcdef1234567890abcdef12345678'
+
+ test('shows wallet address and Prove button after connecting', async ({ page }) => {
+ await page.goto('/')
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+
+ await expect(page.getByText('Connected')).toBeVisible()
+ await expect(page.getByText(MOCK_ADDRESS.slice(0, 6))).toBeVisible()
+ await expect(page.getByText(MOCK_ADDRESS.slice(-4))).toBeVisible()
+ await expect(page.getByRole('button', { name: 'Prove Humanity & Claim' })).toBeVisible()
+ })
+
+ test('shows step indicators after connecting', async ({ page }) => {
+ await page.goto('/')
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+
+ await expect(page.getByText('Challenge')).toBeVisible()
+ await expect(page.getByText('Claim').nth(1)).toBeVisible()
+ })
+
+ test('registration link disappears after connecting', async ({ page }) => {
+ await page.goto('/')
+ await expect(page.getByText('Your agent must be')).toBeVisible()
+
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+ await expect(page.getByText('Your agent must be')).not.toBeVisible()
+ })
+
+ test('shows error when challenge fetch fails during claim', async ({ page }) => {
+ await page.goto('/')
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+
+ await page.route('**/api/challenge', route =>
+ route.fulfill({ status: 500, contentType: 'application/json', body: '{"error":"fail"}' })
+ )
+ await page.getByRole('button', { name: 'Prove Humanity & Claim' }).click()
+
+ await expect(page.getByText('Failed')).toBeVisible({ timeout: 5000 })
+ await expect(page.getByRole('button', { name: 'Try Again' })).toBeVisible()
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 13. REGISTER FORM — Wallet Connection (mocked provider)
+// ---------------------------------------------------------------------------
+test.describe('Register Form — Wallet Interaction', () => {
+ const MOCK_ADDRESS = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
+
+ test('shows wallet address and Verify button after connecting', async ({ page }) => {
+ await page.goto('/register')
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ if (method === 'eth_call') return '0x0000000000000000000000000000000000000000000000000000000000000001'
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+
+ await expect(page.getByText('Wallet')).toBeVisible({ timeout: 5000 })
+ await expect(page.getByText(MOCK_ADDRESS.slice(0, 6))).toBeVisible()
+ })
+
+ test('Orb verification message disappears after connecting', async ({ page }) => {
+ await page.goto('/register')
+ await expect(page.getByText('You need a World ID Orb verification to register.')).toBeVisible()
+
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ if (method === 'eth_call') return '0x0000000000000000000000000000000000000000000000000000000000000001'
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+ await expect(page.getByText('You need a World ID Orb verification to register.')).not.toBeVisible({
+ timeout: 5000,
+ })
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 14. CROSS-CUTTING CONCERNS
+// ---------------------------------------------------------------------------
+test.describe('Cross-Cutting', () => {
+ test('API responses have correct content-type', async ({ request }) => {
+ const endpoints = ['/api/challenge', '/api/rp-context', '/api/status?address=0x0000000000000000000000000000000000000000']
+ for (const endpoint of endpoints) {
+ const res = await request.get(endpoint)
+ expect(res.headers()['content-type']).toContain('application/json')
+ }
+ })
+
+ test('pages return HTML content-type', async ({ request }) => {
+ for (const path of ['/', '/register', '/status']) {
+ const res = await request.get(path)
+ expect(res.headers()['content-type']).toContain('text/html')
+ }
+ })
+
+ test('no console errors on home page (except favicon)', async ({ page }) => {
+ const errors: string[] = []
+ page.on('console', msg => {
+ if (msg.type() === 'error' && !msg.text().includes('favicon')) {
+ errors.push(msg.text())
+ }
+ })
+ await page.goto('/')
+ await page.waitForLoadState('networkidle')
+ expect(errors).toHaveLength(0)
+ })
+
+ test('no console errors on register page (except favicon)', async ({ page }) => {
+ const errors: string[] = []
+ page.on('console', msg => {
+ if (msg.type() === 'error' && !msg.text().includes('favicon')) {
+ errors.push(msg.text())
+ }
+ })
+ await page.goto('/register')
+ await page.waitForLoadState('networkidle')
+ expect(errors).toHaveLength(0)
+ })
+
+ test('no console errors on status page (except favicon)', async ({ page }) => {
+ const errors: string[] = []
+ page.on('console', msg => {
+ if (msg.type() === 'error' && !msg.text().includes('favicon')) {
+ errors.push(msg.text())
+ }
+ })
+ await page.goto('/status')
+ await page.waitForLoadState('networkidle')
+ expect(errors).toHaveLength(0)
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 15. SECURITY — Replay & Tampering
+// ---------------------------------------------------------------------------
+test.describe('Security — Replay & Tampering', () => {
+ test('cannot reuse same challenge nonce', async ({ request }) => {
+ const challengeRes = await request.get('/api/challenge')
+ const challenge = await challengeRes.json()
+
+ const payload = {
+ domain: challenge.domain,
+ address: '0x0000000000000000000000000000000000000000',
+ uri: challenge.uri,
+ version: '1',
+ chainId: challenge.chainId,
+ type: 'eip191',
+ nonce: challenge.nonce,
+ issuedAt: challenge.issuedAt,
+ expirationTime: challenge.expirationTime,
+ signature: '0x' + 'ab'.repeat(65),
+ }
+ const encoded = Buffer.from(JSON.stringify(payload)).toString('base64')
+
+ const res1 = await request.post('/api/claim', { data: { payload: encoded } })
+ const res2 = await request.post('/api/claim', { data: { payload: encoded } })
+
+ const statuses = [res1.status(), res2.status()]
+ expect(statuses.filter(s => s === 200).length).toBeLessThanOrEqual(1)
+ })
+
+ test('tampered payload (modified address) is rejected', async ({ request }) => {
+ const challengeRes = await request.get('/api/challenge')
+ const challenge = await challengeRes.json()
+
+ const originalPayload = {
+ domain: challenge.domain,
+ address: '0x0000000000000000000000000000000000000001',
+ uri: challenge.uri,
+ version: '1',
+ chainId: challenge.chainId,
+ type: 'eip191',
+ nonce: challenge.nonce,
+ issuedAt: challenge.issuedAt,
+ signature: '0x' + 'ab'.repeat(65),
+ }
+
+ const tampered = { ...originalPayload, address: '0x0000000000000000000000000000000000000099' }
+ const encoded = Buffer.from(JSON.stringify(tampered)).toString('base64')
+ const res = await request.post('/api/claim', { data: { payload: encoded } })
+ expect([400, 401]).toContain(res.status())
+ })
+
+ test('payload with wrong domain is rejected', async ({ request }) => {
+ const challengeRes = await request.get('/api/challenge')
+ const challenge = await challengeRes.json()
+
+ const payload = {
+ domain: 'evil-site.com',
+ address: '0x0000000000000000000000000000000000000000',
+ uri: 'https://evil-site.com',
+ version: '1',
+ chainId: challenge.chainId,
+ type: 'eip191',
+ nonce: challenge.nonce,
+ issuedAt: challenge.issuedAt,
+ signature: '0x' + 'ab'.repeat(65),
+ }
+ const encoded = Buffer.from(JSON.stringify(payload)).toString('base64')
+ const res = await request.post('/api/claim', { data: { payload: encoded } })
+ expect(res.status()).toBe(400)
+ const data = await res.json()
+ expect(data.error).toContain('Domain mismatch')
+ })
+
+ test('payload with wrong URI is rejected', async ({ request }) => {
+ const challengeRes = await request.get('/api/challenge')
+ const challenge = await challengeRes.json()
+
+ const payload = {
+ domain: challenge.domain,
+ address: '0x0000000000000000000000000000000000000000',
+ uri: 'https://evil-site.com',
+ version: '1',
+ chainId: challenge.chainId,
+ type: 'eip191',
+ nonce: challenge.nonce,
+ issuedAt: challenge.issuedAt,
+ signature: '0x' + 'ab'.repeat(65),
+ }
+ const encoded = Buffer.from(JSON.stringify(payload)).toString('base64')
+ const res = await request.post('/api/claim', { data: { payload: encoded } })
+ expect(res.status()).toBe(400)
+ const data = await res.json()
+ expect(data.error).toContain('URI mismatch')
+ })
+
+ test('payload with fabricated nonce is rejected', async ({ request }) => {
+ const payload = {
+ domain: 'sybil-airdrop.vercel.app',
+ address: '0x0000000000000000000000000000000000000000',
+ uri: 'https://sybil-airdrop.vercel.app',
+ version: '1',
+ chainId: 'eip155:8453',
+ type: 'eip191',
+ nonce: 'completely-made-up-nonce',
+ issuedAt: new Date().toISOString(),
+ signature: '0x' + 'ab'.repeat(65),
+ }
+ const encoded = Buffer.from(JSON.stringify(payload)).toString('base64')
+ const res = await request.post('/api/claim', { data: { payload: encoded } })
+ expect([400, 401]).toContain(res.status())
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 16. END-TO-END — Full Claim Flow (mocked wallet + mocked APIs)
+// ---------------------------------------------------------------------------
+test.describe('E2E — Full Claim Flow (mocked)', () => {
+ const MOCK_ADDRESS = '0x1234567890abcdef1234567890abcdef12345678'
+ const MOCK_TX_HASH = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
+
+ test('complete claim flow: connect → challenge → sign → claim → success', async ({ page }) => {
+ await page.goto('/')
+
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method, params }: { method: string; params?: any }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ if (method === 'personal_sign') return '0x' + 'ab'.repeat(65)
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+ await expect(page.getByRole('button', { name: 'Prove Humanity & Claim' })).toBeVisible()
+
+ await page.route('**/api/claim', route =>
+ route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({
+ success: true,
+ txHash: MOCK_TX_HASH,
+ amount: '1000000000000000000',
+ agentAddress: MOCK_ADDRESS,
+ explorerUrl: `https://basescan.org/tx/${MOCK_TX_HASH}`,
+ }),
+ })
+ )
+
+ await page.getByRole('button', { name: 'Prove Humanity & Claim' }).click()
+
+ await expect(page.getByText('Airdrop claimed!')).toBeVisible({ timeout: 15000 })
+ await expect(page.getByText(MOCK_TX_HASH.slice(0, 10))).toBeVisible()
+ await expect(page.getByRole('button', { name: 'Claimed' })).toBeDisabled()
+ })
+
+ test('claim flow shows error for unregistered agent', async ({ page }) => {
+ await page.goto('/')
+
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ if (method === 'personal_sign') return '0x' + 'ab'.repeat(65)
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+
+ await page.route('**/api/claim', route =>
+ route.fulfill({
+ status: 403,
+ contentType: 'application/json',
+ body: JSON.stringify({ error: 'Agent is not registered in the AgentBook. Register with World ID first.' }),
+ })
+ )
+
+ await page.getByRole('button', { name: 'Prove Humanity & Claim' }).click()
+
+ await expect(page.getByText('Failed')).toBeVisible({ timeout: 10000 })
+ await expect(page.getByText('not registered')).toBeVisible()
+ await expect(page.getByRole('button', { name: 'Try Again' })).toBeVisible()
+ })
+
+ test('claim flow shows error for already-claimed human', async ({ page }) => {
+ await page.goto('/')
+
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ if (method === 'personal_sign') return '0x' + 'ab'.repeat(65)
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+
+ await page.route('**/api/claim', route =>
+ route.fulfill({
+ status: 409,
+ contentType: 'application/json',
+ body: JSON.stringify({
+ error: 'This human has already claimed the airdrop',
+ txHash: MOCK_TX_HASH,
+ claimedAt: new Date().toISOString(),
+ }),
+ })
+ )
+
+ await page.getByRole('button', { name: 'Prove Humanity & Claim' }).click()
+
+ await expect(page.getByText('Failed')).toBeVisible({ timeout: 10000 })
+ await expect(page.getByText('already claimed')).toBeVisible()
+ })
+
+ test('claim flow handles user rejecting signature', async ({ page }) => {
+ await page.goto('/')
+
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ if (method === 'personal_sign') throw new Error('User rejected the request')
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+ await page.getByRole('button', { name: 'Prove Humanity & Claim' }).click()
+
+ await expect(page.getByText('Failed')).toBeVisible({ timeout: 10000 })
+ await expect(page.getByText('rejected')).toBeVisible()
+ })
+
+ test('Try Again button allows retrying after failure', async ({ page }) => {
+ await page.goto('/')
+
+ await page.evaluate(addr => {
+ ;(window as any).ethereum = {
+ request: async ({ method }: { method: string }) => {
+ if (method === 'eth_requestAccounts') return [addr]
+ if (method === 'personal_sign') return '0x' + 'ab'.repeat(65)
+ return null
+ },
+ }
+ }, MOCK_ADDRESS)
+
+ await page.getByRole('button', { name: 'Connect Wallet' }).click()
+
+ await page.route('**/api/claim', route =>
+ route.fulfill({
+ status: 500,
+ contentType: 'application/json',
+ body: JSON.stringify({ error: 'Internal server error: temporary failure' }),
+ })
+ )
+
+ await page.getByRole('button', { name: 'Prove Humanity & Claim' }).click()
+ await expect(page.getByRole('button', { name: 'Try Again' })).toBeVisible({ timeout: 10000 })
+
+ await page.unroute('**/api/claim')
+ await page.route('**/api/claim', route =>
+ route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({
+ success: true,
+ txHash: MOCK_TX_HASH,
+ amount: '1000000000000000000',
+ agentAddress: MOCK_ADDRESS,
+ explorerUrl: `https://basescan.org/tx/${MOCK_TX_HASH}`,
+ }),
+ })
+ )
+
+ await page.getByRole('button', { name: 'Try Again' }).click()
+ await expect(page.getByText('Airdrop claimed!')).toBeVisible({ timeout: 15000 })
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 17. STATUS PAGE — Registered + Claimed Agent (mocked API)
+// ---------------------------------------------------------------------------
+test.describe('Status Page — Rich Results (mocked)', () => {
+ const MOCK_TX = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
+
+ test('shows full detail for registered + claimed agent', async ({ page }) => {
+ await page.goto('/status')
+
+ await page.route('**/api/status*', route =>
+ route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({
+ registered: true,
+ humanId: '0x1234abcd...ef567890',
+ claimed: true,
+ claim: {
+ txHash: MOCK_TX,
+ amount: '1000000000000000000',
+ claimedAt: '2026-03-09T12:00:00.000Z',
+ explorerUrl: `https://basescan.org/tx/${MOCK_TX}`,
+ },
+ }),
+ })
+ )
+
+ await page.getByPlaceholder('0x...').fill('0x0000000000000000000000000000000000000001')
+ await page.getByRole('button', { name: 'Check' }).click()
+
+ await expect(page.getByText('Yes').first()).toBeVisible({ timeout: 5000 })
+ await expect(page.getByText('0x1234abcd...ef567890')).toBeVisible()
+ await expect(page.getByText('Transaction')).toBeVisible()
+ await expect(page.getByText(MOCK_TX.slice(0, 10))).toBeVisible()
+ await expect(page.getByText('Date')).toBeVisible()
+ await expect(page.getByText('Mar 9, 2026').or(page.getByText('Mar 10, 2026'))).toBeVisible()
+ })
+
+ test('shows registered but not claimed', async ({ page }) => {
+ await page.goto('/status')
+
+ await page.route('**/api/status*', route =>
+ route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({
+ registered: true,
+ humanId: '0xaabbccdd...11223344',
+ claimed: false,
+ claim: null,
+ }),
+ })
+ )
+
+ await page.getByPlaceholder('0x...').fill('0x0000000000000000000000000000000000000002')
+ await page.getByRole('button', { name: 'Check' }).click()
+
+ await expect(page.getByText('0xaabbccdd...11223344')).toBeVisible({ timeout: 5000 })
+
+ const badges = page.locator('text=Yes')
+ await expect(badges.first()).toBeVisible()
+ await expect(page.getByText('No').first()).toBeVisible()
+
+ await expect(page.getByText('Transaction')).not.toBeVisible()
+ })
+})
+
+// ---------------------------------------------------------------------------
+// 18. ACCESSIBILITY BASICS
+// ---------------------------------------------------------------------------
+test.describe('Accessibility', () => {
+ test('all pages have a single h1', async ({ page }) => {
+ for (const path of ['/', '/register', '/status']) {
+ await page.goto(path)
+ const h1s = page.locator('h1')
+ await expect(h1s).toHaveCount(1)
+ }
+ })
+
+ test('navigation uses semantic nav element', async ({ page }) => {
+ await page.goto('/')
+ await expect(page.getByRole('navigation')).toBeVisible()
+ })
+
+ test('main content uses semantic main element', async ({ page }) => {
+ await page.goto('/')
+ await expect(page.getByRole('main')).toBeVisible()
+ })
+
+ test('buttons are focusable and have accessible names', async ({ page }) => {
+ await page.goto('/')
+ const btn = page.getByRole('button', { name: 'Connect Wallet' })
+ await btn.focus()
+ await expect(btn).toBeFocused()
+ })
+
+ test('status input has a placeholder for screen readers', async ({ page }) => {
+ await page.goto('/status')
+ const input = page.getByPlaceholder('0x...')
+ await expect(input).toBeVisible()
+ await input.focus()
+ await expect(input).toBeFocused()
+ })
+
+ test('links have correct href attributes', async ({ page }) => {
+ await page.goto('/')
+ const nav = page.getByRole('navigation')
+ await expect(nav.getByRole('link', { name: 'Claim' })).toHaveAttribute('href', '/')
+ await expect(nav.getByRole('link', { name: 'Register' })).toHaveAttribute('href', '/register')
+ await expect(nav.getByRole('link', { name: 'Status' })).toHaveAttribute('href', '/status')
+ })
+})
diff --git a/sybil-airdrop/tsconfig.json b/sybil-airdrop/tsconfig.json
new file mode 100644
index 0000000..017f353
--- /dev/null
+++ b/sybil-airdrop/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules", "../agentkit"]
+}
diff --git a/sybil-airdrop/vercel.json b/sybil-airdrop/vercel.json
new file mode 100644
index 0000000..f92a3f8
--- /dev/null
+++ b/sybil-airdrop/vercel.json
@@ -0,0 +1,3 @@
+{
+ "framework": "nextjs"
+}
diff --git a/tailwind.config.ts b/tailwind.config.ts
new file mode 100644
index 0000000..eb4df17
--- /dev/null
+++ b/tailwind.config.ts
@@ -0,0 +1,54 @@
+import type { Config } from 'tailwindcss'
+
+const config: Config = {
+ darkMode: 'class',
+ content: [
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
+ './lib/**/*.{js,ts,jsx,tsx,mdx}',
+ ],
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
+ mono: ['JetBrains Mono', 'ui-monospace', 'monospace'],
+ },
+ colors: {
+ primary: {
+ DEFAULT: '#22C55E',
+ dark: '#16A34A',
+ light: '#4ADE80',
+ 50: '#F0FDF4',
+ 100: '#DCFCE7',
+ },
+ surface: {
+ DEFAULT: '#FFFFFF',
+ raised: '#FFFFFF',
+ overlay: '#F8FAFC',
+ },
+ border: {
+ DEFAULT: '#E4E4E7',
+ light: '#F4F4F5',
+ },
+ 'text-primary': '#09090B',
+ 'text-secondary': '#52525B',
+ 'text-muted': '#71717A',
+ success: {
+ DEFAULT: '#22C55E',
+ bg: '#F0FDF4',
+ },
+ error: {
+ DEFAULT: '#09090B',
+ bg: '#FAFAFA',
+ },
+ warning: {
+ DEFAULT: '#09090B',
+ bg: '#FAFAFA',
+ },
+ },
+ },
+ },
+ plugins: [],
+}
+
+export default config
diff --git a/tsconfig.json b/tsconfig.json
index 47bfcc9..417b142 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,10 +1,36 @@
{
- "compilerOptions": {
- "target": "ES2022",
- "module": "ES2022",
- "moduleResolution": "bundler",
- "esModuleInterop": true,
- "strict": true,
- "skipLibCheck": true
- }
+ "compilerOptions": {
+ "target": "ES2020",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "types": ["vitest/globals"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": [
+ "node_modules",
+ "contracts",
+ "scripts",
+ "sybil-airdrop",
+ "prisma",
+ "agentkit",
+ "cli"
+ ]
}
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..f92a3f8
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,3 @@
+{
+ "framework": "nextjs"
+}
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..04f6060
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ environment: 'node',
+ include: ['**/*.test.ts'],
+ },
+ resolve: {
+ alias: {
+ '@': new URL('./', import.meta.url).pathname,
+ },
+ },
+})