diff --git a/apps/extension-wallet/eslint.config.cjs b/apps/extension-wallet/eslint.config.cjs index 0d7e2f9..5b0a5a1 100644 --- a/apps/extension-wallet/eslint.config.cjs +++ b/apps/extension-wallet/eslint.config.cjs @@ -11,20 +11,12 @@ module.exports = [ files: ['**/*.{ts,tsx}'], languageOptions: { parser: tsparser, - globals: { - ...globals.browser, - ...globals.vitest, - }, parserOptions: { ecmaVersion: 2020, sourceType: 'module', ecmaFeatures: { jsx: true, }, - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - ecmaFeatures: { jsx: true }, }, globals: { ...globals.browser, @@ -33,34 +25,34 @@ module.exports = [ }, plugins: { '@typescript-eslint': tseslint, - 'react': react, + react, 'react-hooks': reactHooks, }, rules: { ...tseslint.configs.recommended.rules, ...react.configs.recommended.rules, ...reactHooks.configs.recommended.rules, - 'react/react-in-jsx-scope': 'off', // Not needed in React 18+ - '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + 'no-undef': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], }, settings: { react: { version: 'detect', }, }, - }, - rules: { - ...tseslint.configs.recommended.rules, - 'no-undef': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], - }, }, { files: ['**/__tests__/**/*.{ts,tsx}', '**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}'], languageOptions: { globals: { ...globals.jest, + ...globals.vitest, vi: 'readonly', }, }, diff --git a/apps/extension-wallet/package.json b/apps/extension-wallet/package.json index a04cf80..23cf655 100644 --- a/apps/extension-wallet/package.json +++ b/apps/extension-wallet/package.json @@ -21,7 +21,7 @@ "react-dom": "^18.3.1", "react-error-boundary": "^6.1.1" }, - "devDependencies": { + "devDependencies": { "@eslint/js": "^9.0.0", "@testing-library/jest-dom": "^6.1.0", "@testing-library/react": "^14.0.0", diff --git a/apps/extension-wallet/src/App.tsx b/apps/extension-wallet/src/App.tsx index e7bdc93..33a96e5 100644 --- a/apps/extension-wallet/src/App.tsx +++ b/apps/extension-wallet/src/App.tsx @@ -1,293 +1,5 @@ -/** - * App Component Example - * - * Demonstrates how to use ErrorBoundary and error-handler in a React application. - * This example shows: - * 1. Wrapping the app with ErrorBoundary - * 2. Using error-handler in async functions - * 3. Implementing retry functionality - * 4. Handling different error categories - */ +import { SettingsScreen } from './screens/Settings/SettingsScreen'; -import { useState, useEffect, useCallback } from 'react'; -import { - ErrorBoundary, - useErrorHandler, - handleError, - withErrorHandling, - createRetryable, -} from './errors'; - -/** - * Sample data type - */ -interface UserData { - id: string; - name: string; - balance: string; -} - -/** - * Example component that fetches data - demonstrates async error handling - * Uses the error-handler to classify and log errors - */ -function DataFetcher(): JSX.Element { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - // Use the error handler hook for manual error dispatching - const { reset } = useErrorHandler(); - - const fetchData = useCallback(async () => { - setLoading(true); - setError(null); - - try { - // Simulate a network request that might fail - const response = await fetch('/api/user'); - - if (!response.ok) { - // Use the global error handler to classify the error - const errorInfo = handleError( - new Error(`HTTP ${response.status}: ${response.statusText}`), - 'fetchUserData' - ); - throw new Error(errorInfo.message); - } - - const userData = await response.json(); - setData(userData); - } catch (err) { - const handledError = handleError(err, 'fetchUserData'); - - // Log the error (handled by error-handler internally) - console.log('Error category:', handledError.category); - console.log('Recoverable:', handledError.recoverable); - - setError(handledError.originalError as Error); - } finally { - setLoading(false); - } - }, []); - - // Initial fetch on mount - useEffect(() => { - fetchData(); - }, [fetchData]); - - if (error) { - return ( -
-

Error: {error.message}

-
- - -
-
- ); - } - - if (loading) { - return
Loading...
; - } - - return ( -
-

User Data

- {data && ( -
    -
  • ID: {data.id}
  • -
  • Name: {data.name}
  • -
  • Balance: {data.balance}
  • -
- )} - -
- ); -} - -/** - * Example component using withErrorHandling HOC - * Wraps an async function with automatic error handling - */ -async function fetchUserBalance(userId: string): Promise { - // Simulate network call - const response = await fetch(`/api/balance/${userId}`); - - if (!response.ok) { - throw new Error('Failed to fetch balance'); - } - - const data = await response.json(); - return data.balance; -} - -// Wrap function with error handling (example utility export usage) -void withErrorHandling(fetchUserBalance as any, 'fetchUserBalance'); - -/** - * Example component using createRetryable - * Creates a function that automatically retries on failure - */ -async function submitTransaction(txData: object): Promise<{ txHash: string }> { - // Simulate transaction submission - const response = await fetch('/api/submit', { - method: 'POST', - body: JSON.stringify(txData), - }); - - if (!response.ok) { - throw new Error('Transaction failed'); - } - - return response.json(); +export function App() { + return ; } - -// Create a retryable version that retries up to 3 times -const submitTransactionWithRetry = createRetryable(submitTransaction as any, 3, 1000); - -/** - * Transaction component - demonstrates retry functionality - */ -function TransactionComponent(): JSX.Element { - const [status, setStatus] = useState('idle'); - const [txHash, setTxHash] = useState(null); - - const handleSubmit = async () => { - setStatus('submitting'); - - const result = (await submitTransactionWithRetry({ amount: 100 })) as { txHash: string }; - - if ('txHash' in result) { - setTxHash(result.txHash); - setStatus('success'); - } else { - setStatus('failed'); - } - }; - - return ( -
-

Transaction

-

Status: {status}

- {txHash &&

Tx Hash: {txHash}

} - -
- ); -} - -/** - * Main App component - wrapped with ErrorBoundary - * The ErrorBoundary will catch any rendering errors in children - */ -export function App(): JSX.Element { - // Callback for handling errors that escape component boundaries - const handleAppError = (error: Error, errorInfo: React.ErrorInfo) => { - console.error('App-level error:', error, errorInfo.componentStack); - }; - - // Callback for resetting app state - const handleReset = () => { - console.log('App reset requested'); - }; - - return ( -
-

Extension Wallet

- - {/* Wrap the entire app with ErrorBoundary */} - -
- {/* Example 1: Data fetching with manual error handling */} -
-

Data Fetcher Example

- -
- - {/* Example 2: Transaction with retry */} -
-

Transaction Example (with retry)

- -
- - {/* Example 3: Direct error handler usage */} -
-

Direct Error Handler Example

- -
-
-
-
- ); -} - -/** - * Component demonstrating direct use of error-handler - */ -function DirectErrorExample(): JSX.Element { - const [result, setResult] = useState(null); - - const testNetworkError = () => { - const errorInfo = handleError(new Error('ECONNREFUSED: Connection refused'), 'networkTest'); - setResult(`Category: ${errorInfo.category}, Recoverable: ${errorInfo.recoverable}`); - }; - - const testValidationError = () => { - const errorInfo = handleError( - new Error('validation failed: invalid address'), - 'validationTest' - ); - setResult(`Category: ${errorInfo.category}, Recoverable: ${errorInfo.recoverable}`); - }; - - const testContractError = () => { - const errorInfo = handleError(new Error('Contract: execution reverted'), 'contractTest'); - setResult(`Category: ${errorInfo.category}, Recoverable: ${errorInfo.recoverable}`); - }; - - const testUnknownError = () => { - const errorInfo = handleError(new Error('Something unexpected'), 'unknownTest'); - setResult(`Category: ${errorInfo.category}, Recoverable: ${errorInfo.recoverable}`); - }; - - return ( -
-
- - - - -
- {result &&

{result}

} -
- ); -} - -export default App; diff --git a/apps/extension-wallet/src/components/PaymentQRCode.tsx b/apps/extension-wallet/src/components/PaymentQRCode.tsx index ee323ce..b608685 100644 --- a/apps/extension-wallet/src/components/PaymentQRCode.tsx +++ b/apps/extension-wallet/src/components/PaymentQRCode.tsx @@ -1,4 +1,3 @@ -import * as React from 'react'; import { QRCodeSVG } from 'qrcode.react'; import { cn } from '@ancore/ui-kit'; @@ -22,11 +21,7 @@ export interface PaymentQRCodeProps { * Uses qrcode.react under the hood with a styled card border so it fits * the extension-wallet design system. */ -export function PaymentQRCode({ - value, - size = 220, - className, -}: PaymentQRCodeProps) { +export function PaymentQRCode({ value, size = 220, className }: PaymentQRCodeProps) { return (
( - Component: React.ComponentType

, + Component: ComponentType

, errorBoundaryProps?: Partial -): React.ComponentType

{ +): ComponentType

{ return function WrappedComponent(props: P): JSX.Element { return ( diff --git a/apps/extension-wallet/src/errors/ErrorScreen.tsx b/apps/extension-wallet/src/errors/ErrorScreen.tsx index 1adf2d3..59836c3 100644 --- a/apps/extension-wallet/src/errors/ErrorScreen.tsx +++ b/apps/extension-wallet/src/errors/ErrorScreen.tsx @@ -7,7 +7,7 @@ import { Button } from '@ancore/ui-kit'; import { AlertTriangle, RotateCcw, RefreshCw, Info } from 'lucide-react'; -import { ReactNode } from 'react'; +import type { JSX, ReactNode } from 'react'; import { getErrorMessage, ErrorCategory } from './error-messages'; import { ErrorInfo, handleError } from './error-handler'; diff --git a/apps/extension-wallet/src/errors/__tests__/errors.test.ts b/apps/extension-wallet/src/errors/__tests__/errors.test.ts index 35f644e..6bd02a7 100644 --- a/apps/extension-wallet/src/errors/__tests__/errors.test.ts +++ b/apps/extension-wallet/src/errors/__tests__/errors.test.ts @@ -99,7 +99,7 @@ describe('ErrorHandler', () => { it('should extract node error code property', () => { const error = new Error('Test error'); - (error as any).code = 'ENOENT'; + (error as Error & { code?: string }).code = 'ENOENT'; const code = errorHandler.extractErrorCode(error); expect(code).toBe('ENOENT'); }); @@ -293,7 +293,7 @@ describe('Recovery functionality', () => { const result = await retryableFn(); expect(result).toHaveProperty('category'); - expect((result as any).category).toBe(ErrorCategory.VALIDATION); + expect((result as { category: ErrorCategory }).category).toBe(ErrorCategory.VALIDATION); }); }); }); diff --git a/apps/extension-wallet/src/main.tsx b/apps/extension-wallet/src/main.tsx index a3da5de..ba3754b 100644 --- a/apps/extension-wallet/src/main.tsx +++ b/apps/extension-wallet/src/main.tsx @@ -7,16 +7,22 @@ import './index.css'; function App() { const [view, setView] = useState<'receive' | 'settings'>('receive'); - const [network, setNetwork] = useState<'mainnet' | 'testnet' | 'futurenet'>('testnet'); + const [network] = useState<'mainnet' | 'testnet' | 'futurenet'>('testnet'); return (

{/* Simple Navigation for development */}
- -
@@ -43,4 +49,4 @@ ReactDOM.createRoot(document.getElementById('root')!).render( -); \ No newline at end of file +); diff --git a/apps/extension-wallet/src/screens/ReceiveScreen.tsx b/apps/extension-wallet/src/screens/ReceiveScreen.tsx index f0a443b..170e6d0 100644 --- a/apps/extension-wallet/src/screens/ReceiveScreen.tsx +++ b/apps/extension-wallet/src/screens/ReceiveScreen.tsx @@ -68,19 +68,11 @@ export function ReceiveScreen({ }, []); return ( - + {/* ── Header ─────────────────────────────────────────────────── */}
- Receive @@ -116,12 +108,7 @@ export function ReceiveScreen({ {account.name}

)} - +
{/* Print button */} diff --git a/apps/extension-wallet/src/screens/Settings/SecuritySettings.tsx b/apps/extension-wallet/src/screens/Settings/SecuritySettings.tsx index c2361dd..580d7b2 100644 --- a/apps/extension-wallet/src/screens/Settings/SecuritySettings.tsx +++ b/apps/extension-wallet/src/screens/Settings/SecuritySettings.tsx @@ -70,8 +70,8 @@ function ChangePasswordView({ onDone }: { onDone: () => void }) { type="password" placeholder="Enter current password" value={form.current} - onChange={(e: React.ChangeEvent) => - setForm((f) => ({ ...f, current: e.target.value })) + onChange={(event: React.ChangeEvent) => + setForm((formState) => ({ ...formState, current: event.target.value })) } />
@@ -84,8 +84,8 @@ function ChangePasswordView({ onDone }: { onDone: () => void }) { type={showNext ? 'text' : 'password'} placeholder="Min. 8 characters" value={form.next} - onChange={(e: React.ChangeEvent) => - setForm((f) => ({ ...f, next: e.target.value })) + onChange={(event: React.ChangeEvent) => + setForm((formState) => ({ ...formState, next: event.target.value })) } className="pr-10" /> @@ -106,8 +106,8 @@ function ChangePasswordView({ onDone }: { onDone: () => void }) { type="password" placeholder="Repeat new password" value={form.confirm} - onChange={(e: React.ChangeEvent) => - setForm((f) => ({ ...f, confirm: e.target.value })) + onChange={(event: React.ChangeEvent) => + setForm((formState) => ({ ...formState, confirm: event.target.value })) } />
@@ -259,7 +259,7 @@ function ExportWarningView({ type="password" placeholder="Enter password to continue" value={password} - onChange={(e: React.ChangeEvent) => setPassword(e.target.value)} + onChange={(event: React.ChangeEvent) => setPassword(event.target.value)} /> {error &&

{error}

} diff --git a/apps/extension-wallet/src/screens/__tests__/ReceiveScreen.test.tsx b/apps/extension-wallet/src/screens/__tests__/ReceiveScreen.test.tsx index 838e528..f4d285f 100644 --- a/apps/extension-wallet/src/screens/__tests__/ReceiveScreen.test.tsx +++ b/apps/extension-wallet/src/screens/__tests__/ReceiveScreen.test.tsx @@ -1,9 +1,7 @@ -import * as React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { vi } from 'vitest'; - vi.mock('qrcode.react', () => ({ QRCodeSVG: ({ value, 'aria-label': ariaLabel }: { value: string; 'aria-label'?: string }) => ( @@ -12,7 +10,6 @@ vi.mock('qrcode.react', () => ({ import { ReceiveScreen } from '@/screens/ReceiveScreen'; - const MAINNET_ACCOUNT = { publicKey: 'GABC1234567890DEFGHIJKLMNOPQRSTUVWXYZ', name: 'Primary Wallet', @@ -27,9 +24,7 @@ describe('ReceiveScreen', () => { describe('rendering', () => { it('renders the screen title', () => { render(); - expect( - screen.getByRole('heading', { name: /receive/i }) - ).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /receive/i })).toBeInTheDocument(); }); it('renders the QR code with the correct address value', () => { @@ -73,9 +68,7 @@ describe('ReceiveScreen', () => { }); it('shows "Futurenet" badge when network is futurenet', () => { - render( - - ); + render(); expect(screen.getByText('Futurenet')).toBeInTheDocument(); }); }); @@ -117,9 +110,7 @@ describe('ReceiveScreen', () => { await waitFor(() => { // After copying the button aria-label changes inside AddressDisplay // The icon swaps to a check – verify the button is still in the DOM - expect( - screen.getByRole('button', { name: /copy address/i }) - ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /copy address/i })).toBeInTheDocument(); }); }); }); @@ -127,14 +118,12 @@ describe('ReceiveScreen', () => { describe('print', () => { it('renders a print button', () => { render(); - expect( - screen.getByRole('button', { name: /print qr code/i }) - ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /print qr code/i })).toBeInTheDocument(); }); it('calls window.print when the print button is clicked', async () => { const user = userEvent.setup(); - const printSpy = vi.spyOn(window, 'print').mockImplementation(() => { }); + const printSpy = vi.spyOn(window, 'print').mockImplementation(() => {}); render(); await user.click(screen.getByRole('button', { name: /print qr code/i })); @@ -146,13 +135,9 @@ describe('ReceiveScreen', () => { describe('QR generation', () => { it('encodes exactly the publicKey in the QR code', () => { - const publicKey = - 'GD6SZQJNKL3ZYXPWLUVFXZNXUVXJTQPWMQHZMDMQHLS5VNLQBQNPFLM'; + const publicKey = 'GD6SZQJNKL3ZYXPWLUVFXZNXUVXJTQPWMQHZMDMQHLS5VNLQBQNPFLM'; render(); - expect(screen.getByTestId('qr-code-svg')).toHaveAttribute( - 'data-value', - publicKey - ); + expect(screen.getByTestId('qr-code-svg')).toHaveAttribute('data-value', publicKey); }); it('renders a different QR value when account changes', () => { diff --git a/apps/extension-wallet/tailwind.config.js b/apps/extension-wallet/tailwind.config.js index d0de3d5..f2ff762 100644 --- a/apps/extension-wallet/tailwind.config.js +++ b/apps/extension-wallet/tailwind.config.js @@ -1,5 +1,7 @@ +import tailwindcssAnimate from 'tailwindcss-animate'; + /** @type {import('tailwindcss').Config} */ -export default { +const config = { darkMode: ['class'], content: ['./index.html', './src/**/*.{ts,tsx}', '../../packages/ui-kit/src/**/*.{ts,tsx}'], theme: { @@ -42,5 +44,7 @@ export default { }, }, }, - plugins: [require('tailwindcss-animate')], -}; \ No newline at end of file + plugins: [tailwindcssAnimate], +}; + +export default config; diff --git a/apps/extension-wallet/vite.config.ts b/apps/extension-wallet/vite.config.ts index 14729eb..b38d17f 100644 --- a/apps/extension-wallet/vite.config.ts +++ b/apps/extension-wallet/vite.config.ts @@ -1,15 +1,16 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; -import path from 'path'; +import { resolve } from 'path'; +import { fileURLToPath } from 'url'; + +const rootDir = fileURLToPath(new URL('.', import.meta.url)); export default defineConfig({ plugins: [react()], resolve: { - alias: { - '@': path.resolve(__dirname, './src'), - }, + alias: { '@': resolve(rootDir, 'src') }, }, css: { postcss: './postcss.config.js', }, -}); \ No newline at end of file +}); diff --git a/apps/extension-wallet/vitest.config.ts b/apps/extension-wallet/vitest.config.ts index 00957ab..4106231 100644 --- a/apps/extension-wallet/vitest.config.ts +++ b/apps/extension-wallet/vitest.config.ts @@ -1,6 +1,9 @@ import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; import path from 'path'; +import { fileURLToPath } from 'url'; + +const rootDir = fileURLToPath(new URL('.', import.meta.url)); export default defineConfig({ plugins: [react()], @@ -12,7 +15,7 @@ export default defineConfig({ }, resolve: { alias: { - '@': path.resolve(__dirname, './src'), + '@': path.resolve(rootDir, './src'), }, }, }); diff --git a/contracts/account/src/lib.rs b/contracts/account/src/lib.rs index b3edb13..e8c3579 100644 --- a/contracts/account/src/lib.rs +++ b/contracts/account/src/lib.rs @@ -159,6 +159,10 @@ impl AncoreAccount { } + if current_nonce != expected_nonce { + return Err(ContractError::InvalidNonce); + } + // Increment nonce env.storage().instance().set(&DataKey::Nonce, &(current_nonce + 1)); diff --git a/contracts/account/test_snapshots/test/test_execute_rejects_invalid_nonce.1.json b/contracts/account/test_snapshots/test/test_execute_rejects_invalid_nonce.1.json index acdd317..7848606 100644 --- a/contracts/account/test_snapshots/test/test_execute_rejects_invalid_nonce.1.json +++ b/contracts/account/test_snapshots/test/test_execute_rejects_invalid_nonce.1.json @@ -210,27 +210,16 @@ "v0": { "topics": [ { - "symbol": "log" + "symbol": "fn_return" + }, + { + "symbol": "execute" } ], "data": { - "vec": [ - { - "string": "caught panic 'Invalid nonce' from contract function 'Symbol(execute)'" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" - }, - { - "symbol": "transfer" - }, - { - "vec": [] - }, - { - "u64": 1 - } - ] + "error": { + "contract": 4 + } } } } @@ -250,12 +239,12 @@ }, { "error": { - "wasm_vm": "invalid_action" + "contract": 4 } } ], "data": { - "string": "caught error from function" + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" } } } @@ -275,7 +264,7 @@ }, { "error": { - "wasm_vm": "invalid_action" + "contract": 4 } } ], @@ -323,7 +312,7 @@ }, { "error": { - "wasm_vm": "invalid_action" + "contract": 4 } } ], diff --git a/packages/account-abstraction/eslint.config.cjs b/packages/account-abstraction/eslint.config.cjs index e62e4f9..dc37024 100644 --- a/packages/account-abstraction/eslint.config.cjs +++ b/packages/account-abstraction/eslint.config.cjs @@ -6,6 +6,7 @@ const nodeGlobals = { Buffer: 'readonly', console: 'readonly', process: 'readonly', + TextEncoder: 'readonly', setTimeout: 'readonly', clearTimeout: 'readonly', setInterval: 'readonly', diff --git a/packages/account-abstraction/src/__tests__/xdr-utils.test.ts b/packages/account-abstraction/src/__tests__/xdr-utils.test.ts new file mode 100644 index 0000000..f279c08 --- /dev/null +++ b/packages/account-abstraction/src/__tests__/xdr-utils.test.ts @@ -0,0 +1,114 @@ +import { Address, xdr } from '@stellar/stellar-sdk'; + +import { + bytes32ScValToPublicKey, + decodeAddSessionKeyArgs, + decodeExecuteArgs, + decodeExecuteResult, + decodeGetSessionKeyArgs, + decodeInitializeArgs, + decodeNonceResult, + decodeOwnerResult, + decodeRevokeSessionKeyArgs, + decodeSessionKeyResult, + decodeVoidResult, + encodeAddSessionKeyArgs, + encodeExecuteArgs, + encodeGetSessionKeyArgs, + encodeInitializeArgs, + encodeRevokeSessionKeyArgs, + sessionKeyToScVal, +} from '../xdr-utils'; + +const OWNER_ADDRESS = 'GCM5WPR4DDR24FSAX5LIEM4J7AI3KOWJYANSXEPKYXCSZOTAYXE75AFN'; +const CONTRACT_ADDRESS = 'CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE'; + +describe('account abstraction XDR helpers', () => { + it('round-trips initialize args', () => { + const encoded = encodeInitializeArgs({ owner: OWNER_ADDRESS }); + + expect(decodeInitializeArgs(encoded)).toEqual({ owner: OWNER_ADDRESS }); + }); + + it('round-trips execute args', () => { + const innerArgs = [ + new Address(OWNER_ADDRESS).toScVal(), + xdr.ScVal.scvU32(7), + xdr.ScVal.scvBool(true), + ]; + + const encoded = encodeExecuteArgs({ + to: CONTRACT_ADDRESS, + functionName: 'transfer', + args: innerArgs, + expectedNonce: 9, + }); + + const decoded = decodeExecuteArgs(encoded); + + expect(decoded.to).toBe(CONTRACT_ADDRESS); + expect(decoded.functionName).toBe('transfer'); + expect(decoded.expectedNonce).toBe(9); + expect(decoded.args).toHaveLength(3); + }); + + it('round-trips add_session_key args', () => { + const encoded = encodeAddSessionKeyArgs({ + publicKey: OWNER_ADDRESS, + expiresAt: 1700000000, + permissions: [0, 2], + }); + + expect(decodeAddSessionKeyArgs(encoded)).toEqual({ + publicKey: OWNER_ADDRESS, + expiresAt: 1700000000, + permissions: [0, 2], + }); + }); + + it('round-trips revoke_session_key args', () => { + const encoded = encodeRevokeSessionKeyArgs({ publicKey: OWNER_ADDRESS }); + + expect(decodeRevokeSessionKeyArgs(encoded)).toEqual({ + publicKey: OWNER_ADDRESS, + }); + }); + + it('round-trips get_session_key args', () => { + const encoded = encodeGetSessionKeyArgs({ publicKey: OWNER_ADDRESS }); + + expect(decodeGetSessionKeyArgs(encoded)).toEqual({ + publicKey: OWNER_ADDRESS, + }); + }); + + it('round-trips session key result decoding', () => { + const scVal = sessionKeyToScVal({ + publicKey: OWNER_ADDRESS, + expiresAt: 1700000000, + permissions: [0, 1, 2], + }); + + expect(decodeSessionKeyResult(scVal)).toEqual({ + publicKey: OWNER_ADDRESS, + expiresAt: 1700000000, + permissions: [0, 1, 2], + }); + }); + + it('decodes owner and nonce results', () => { + expect(decodeOwnerResult(new Address(OWNER_ADDRESS).toScVal())).toBe(OWNER_ADDRESS); + expect(decodeNonceResult(xdr.ScVal.scvU64(new xdr.Uint64(42n)))).toBe(42); + }); + + it('decodes execute and void results', () => { + expect(decodeExecuteResult(xdr.ScVal.scvBool(true))).toBe(true); + expect(() => decodeVoidResult(xdr.ScVal.scvVoid())).not.toThrow(); + }); + + it('preserves public key bytes deterministically', () => { + const encoded = encodeGetSessionKeyArgs({ publicKey: OWNER_ADDRESS }); + + expect(bytes32ScValToPublicKey(encoded[0])).toBe(OWNER_ADDRESS); + }); +}); diff --git a/packages/account-abstraction/src/account-contract.ts b/packages/account-abstraction/src/account-contract.ts index 242e9f1..3ec7344 100644 --- a/packages/account-abstraction/src/account-contract.ts +++ b/packages/account-abstraction/src/account-contract.ts @@ -7,21 +7,21 @@ import type { SessionKey } from '@ancore/types'; import { Account, Contract, TransactionBuilder, xdr } from '@stellar/stellar-sdk'; import { mapContractError } from './errors'; import { - addressToScVal, - permissionsToScVal, - publicKeyToBytes32ScVal, - scValToAddress, - scValToOptionalSessionKey, - scValToU64, - symbolToScVal, - u64ToScVal, + decodeNonceResult, + decodeOwnerResult, + decodeSessionKeyResult, + encodeAddSessionKeyArgs, + encodeExecuteArgs, + encodeGetSessionKeyArgs, + encodeInitializeArgs, + encodeRevokeSessionKeyArgs, } from './xdr-utils'; /** Options for read calls (getOwner, getNonce, getSessionKey) when using a server */ export interface AccountContractReadOptions { server: { getAccount(accountId: string): Promise<{ id: string; sequence: string }>; - simulateTransaction(tx: unknown): Promise; + simulateTransaction(tx: unknown): Promise; }; sourceAccount: string; /** Network passphrase (e.g. Networks.TESTNET); required for building the simulation tx */ @@ -34,6 +34,14 @@ export interface InvocationArgs { args: xdr.ScVal[]; } +interface SimulationResponse { + error?: string; + message?: string; + result?: { + retval?: xdr.ScVal; + }; +} + /** * AccountContract wraps the Ancore account abstraction contract (contracts/account). * Use it to build invoke operations or to run read-only calls via a Soroban RPC server. @@ -54,7 +62,7 @@ export class AccountContract { initialize(owner: string): InvocationArgs { return { method: 'initialize', - args: [addressToScVal(owner)], + args: encodeInitializeArgs({ owner }), }; } @@ -65,12 +73,12 @@ export class AccountContract { execute(to: string, fn: string, args: xdr.ScVal[], expectedNonce: number): InvocationArgs { return { method: 'execute', - args: [ - addressToScVal(to), - symbolToScVal(fn), - xdr.ScVal.scvVec(args), - u64ToScVal(expectedNonce), - ], + args: encodeExecuteArgs({ + to, + functionName: fn, + args, + expectedNonce, + }), }; } @@ -85,11 +93,11 @@ export class AccountContract { ): InvocationArgs { return { method: 'add_session_key', - args: [ - publicKeyToBytes32ScVal(publicKey), - u64ToScVal(expiresAt), - permissionsToScVal(permissions), - ], + args: encodeAddSessionKeyArgs({ + publicKey, + expiresAt, + permissions, + }), }; } @@ -99,7 +107,7 @@ export class AccountContract { revokeSessionKey(publicKey: string | Uint8Array): InvocationArgs { return { method: 'revoke_session_key', - args: [publicKeyToBytes32ScVal(publicKey)], + args: encodeRevokeSessionKeyArgs({ publicKey }), }; } @@ -110,7 +118,7 @@ export class AccountContract { getSessionKeyInvocation(publicKey: string | Uint8Array): InvocationArgs { return { method: 'get_session_key', - args: [publicKeyToBytes32ScVal(publicKey)], + args: encodeGetSessionKeyArgs({ publicKey }), }; } @@ -147,7 +155,7 @@ export class AccountContract { */ async getOwner(options: AccountContractReadOptions): Promise { const result = await this.simulateRead('get_owner', [], options); - return scValToAddress(result); + return decodeOwnerResult(result); } /** @@ -155,7 +163,7 @@ export class AccountContract { */ async getNonce(options: AccountContractReadOptions): Promise { const result = await this.simulateRead('get_nonce', [], options); - return scValToU64(result); + return decodeNonceResult(result); } /** @@ -168,10 +176,10 @@ export class AccountContract { ): Promise { const result = await this.simulateRead( 'get_session_key', - [publicKeyToBytes32ScVal(publicKey)], + encodeGetSessionKeyArgs({ publicKey }), options ); - return scValToOptionalSessionKey(result); + return decodeSessionKeyResult(result); } /** @@ -198,7 +206,7 @@ export class AccountContract { const raw = txBuilder.build(); - const sim: any = await server.simulateTransaction(raw); + const sim = await server.simulateTransaction(raw); if (sim && typeof sim === 'object' && ('error' in sim || 'message' in sim)) { const errMsg = @@ -208,7 +216,7 @@ export class AccountContract { throw mapContractError(String(errMsg), sim); } - const result = (sim as any)?.result?.retval as xdr.ScVal | undefined; + const result = sim.result?.retval; if (result === undefined) { throw mapContractError('No return value from simulation', sim); } diff --git a/packages/account-abstraction/src/index.ts b/packages/account-abstraction/src/index.ts index 3bd420e..e5767ff 100644 --- a/packages/account-abstraction/src/index.ts +++ b/packages/account-abstraction/src/index.ts @@ -23,13 +23,29 @@ export { export { addressToScVal, + decodeAddSessionKeyArgs, + decodeExecuteArgs, + decodeExecuteResult, + decodeGetSessionKeyArgs, + decodeInitializeArgs, + decodeNonceResult, + decodeOwnerResult, + decodeRevokeSessionKeyArgs, + decodeSessionKeyResult, + decodeVoidResult, publicKeyToBytes32ScVal, u64ToScVal, permissionsToScVal, symbolToScVal, + encodeAddSessionKeyArgs, + encodeExecuteArgs, + encodeGetSessionKeyArgs, + encodeInitializeArgs, + encodeRevokeSessionKeyArgs, scValToAddress, scValToU64, bytes32ScValToPublicKey, scValToSessionKey, scValToOptionalSessionKey, + sessionKeyToScVal, } from './xdr-utils'; diff --git a/packages/account-abstraction/src/xdr-utils.ts b/packages/account-abstraction/src/xdr-utils.ts index a4e2e42..f70b87c 100644 --- a/packages/account-abstraction/src/xdr-utils.ts +++ b/packages/account-abstraction/src/xdr-utils.ts @@ -8,6 +8,34 @@ import { Address, nativeToScVal, scValToNative, StrKey, xdr } from '@stellar/ste const BYTES_N_32_LENGTH = 32; +export interface InitializeParams { + owner: string; +} + +export interface ExecuteParams { + to: string; + functionName: string; + args: xdr.ScVal[]; + expectedNonce: number | bigint; +} + +export interface SessionKeyParams { + publicKey: string | Uint8Array; +} + +export interface AddSessionKeyParams extends SessionKeyParams { + expiresAt: number | bigint; + permissions: number[]; +} + +function assertArgumentCount(args: xdr.ScVal[], expected: number, method: string): void { + if (args.length !== expected) { + throw new TypeError( + `${method} expects ${expected} argument${expected === 1 ? '' : 's'}, got ${args.length}` + ); + } +} + /** * Encode a Stellar address (G... or C...) to ScVal for contract Address type. */ @@ -60,6 +88,135 @@ export function symbolToScVal(name: string): xdr.ScVal { return xdr.ScVal.scvSymbol(Buffer.from(name, 'utf8')); } +/** + * Encode initialize(owner) invocation args. + */ +export function encodeInitializeArgs({ owner }: InitializeParams): xdr.ScVal[] { + return [addressToScVal(owner)]; +} + +/** + * Decode initialize(owner) invocation args. + */ +export function decodeInitializeArgs(args: xdr.ScVal[]): InitializeParams { + assertArgumentCount(args, 1, 'initialize'); + return { owner: scValToAddress(args[0]) }; +} + +/** + * Encode execute(to, function, args, expected_nonce) invocation args. + */ +export function encodeExecuteArgs({ + to, + functionName, + args, + expectedNonce, +}: ExecuteParams): xdr.ScVal[] { + return [ + addressToScVal(to), + symbolToScVal(functionName), + xdr.ScVal.scvVec(args), + u64ToScVal(expectedNonce), + ]; +} + +/** + * Decode execute(to, function, args, expected_nonce) invocation args. + */ +export function decodeExecuteArgs(args: xdr.ScVal[]): ExecuteParams { + assertArgumentCount(args, 4, 'execute'); + + const fn = scValToNative(args[1]); + if (typeof fn !== 'string') { + throw new TypeError('execute function name must decode to a string'); + } + + const nativeArgs = scValToNative(args[2]); + if (!Array.isArray(nativeArgs)) { + throw new TypeError('execute args must decode to an array'); + } + + return { + to: scValToAddress(args[0]), + functionName: fn, + args: args[2].vec() ?? [], + expectedNonce: scValToU64(args[3]), + }; +} + +/** + * Encode add_session_key(public_key, expires_at, permissions) invocation args. + */ +export function encodeAddSessionKeyArgs({ + publicKey, + expiresAt, + permissions, +}: AddSessionKeyParams): xdr.ScVal[] { + return [ + publicKeyToBytes32ScVal(publicKey), + u64ToScVal(expiresAt), + permissionsToScVal(permissions), + ]; +} + +/** + * Decode add_session_key(public_key, expires_at, permissions) invocation args. + */ +export function decodeAddSessionKeyArgs(args: xdr.ScVal[]): { + publicKey: string; + expiresAt: number; + permissions: number[]; +} { + assertArgumentCount(args, 3, 'add_session_key'); + + const nativePermissions = scValToNative(args[2]); + if (!Array.isArray(nativePermissions)) { + throw new TypeError('permissions must decode to an array'); + } + + return { + publicKey: bytes32ScValToPublicKey(args[0]), + expiresAt: scValToU64(args[1]), + permissions: nativePermissions.map((permission) => + typeof permission === 'bigint' ? Number(permission) : Number(permission) + ), + }; +} + +/** + * Encode revoke_session_key(public_key) invocation args. + */ +export function encodeRevokeSessionKeyArgs({ publicKey }: SessionKeyParams): xdr.ScVal[] { + return [publicKeyToBytes32ScVal(publicKey)]; +} + +/** + * Decode revoke_session_key(public_key) invocation args. + */ +export function decodeRevokeSessionKeyArgs(args: xdr.ScVal[]): { + publicKey: string; +} { + assertArgumentCount(args, 1, 'revoke_session_key'); + return { publicKey: bytes32ScValToPublicKey(args[0]) }; +} + +/** + * Encode get_session_key(public_key) invocation args. + */ +export function encodeGetSessionKeyArgs({ publicKey }: SessionKeyParams): xdr.ScVal[] { + return [publicKeyToBytes32ScVal(publicKey)]; +} + +/** + * Decode get_session_key(public_key) invocation args. + */ +export function decodeGetSessionKeyArgs(args: xdr.ScVal[]): { + publicKey: string; +} { + assertArgumentCount(args, 1, 'get_session_key'); + return { publicKey: bytes32ScValToPublicKey(args[0]) }; +} + /** * Decode ScVal to Stellar address string. */ @@ -151,3 +308,65 @@ export function scValToOptionalSessionKey(scVal: xdr.ScVal): SessionKey | null { return null; } } + +/** + * Encode a SessionKey struct to ScVal using deterministic field order. + */ +export function sessionKeyToScVal(sessionKey: SessionKey): xdr.ScVal { + return xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: symbolToScVal('expires_at'), + val: u64ToScVal(sessionKey.expiresAt), + }), + new xdr.ScMapEntry({ + key: symbolToScVal('permissions'), + val: permissionsToScVal(sessionKey.permissions), + }), + new xdr.ScMapEntry({ + key: symbolToScVal('public_key'), + val: publicKeyToBytes32ScVal(sessionKey.publicKey), + }), + ]); +} + +/** + * Decode get_owner result. + */ +export function decodeOwnerResult(scVal: xdr.ScVal): string { + return scValToAddress(scVal); +} + +/** + * Decode get_nonce result. + */ +export function decodeNonceResult(scVal: xdr.ScVal): number { + return scValToU64(scVal); +} + +/** + * Decode execute result. + */ +export function decodeExecuteResult(scVal: xdr.ScVal): boolean { + const native = scValToNative(scVal); + if (typeof native !== 'boolean') { + throw new TypeError('Expected boolean execute result from ScVal'); + } + return native; +} + +/** + * Decode get_session_key result. + */ +export function decodeSessionKeyResult(scVal: xdr.ScVal): SessionKey | null { + return scValToOptionalSessionKey(scVal); +} + +/** + * Decode unit/void result for mutation calls. + */ +export function decodeVoidResult(scVal: xdr.ScVal): void { + const native = scValToNative(scVal); + if (native !== undefined && native !== null) { + throw new TypeError('Expected void result from ScVal'); + } +} diff --git a/packages/core-sdk/eslint.config.cjs b/packages/core-sdk/eslint.config.cjs index bd157db..4c86b26 100644 --- a/packages/core-sdk/eslint.config.cjs +++ b/packages/core-sdk/eslint.config.cjs @@ -26,10 +26,13 @@ module.exports = [ }, globals: { Buffer: 'readonly', + BufferSource: 'readonly', TextEncoder: 'readonly', TextDecoder: 'readonly', CryptoKey: 'readonly', crypto: 'readonly', + console: 'readonly', + process: 'readonly', }, }, plugins: { diff --git a/packages/core-sdk/src/account-transaction-builder.ts b/packages/core-sdk/src/account-transaction-builder.ts index d1508b0..608bdd8 100644 --- a/packages/core-sdk/src/account-transaction-builder.ts +++ b/packages/core-sdk/src/account-transaction-builder.ts @@ -72,7 +72,6 @@ export class AccountTransactionBuilder { private readonly txBuilder: TransactionBuilder; private readonly server: rpc.Server; private readonly contract: Contract; - private readonly networkPassphrase: string; private readonly timeoutSeconds: number; /** Track whether at least one operation has been added. */ @@ -99,7 +98,6 @@ export class AccountTransactionBuilder { this.server = server; this.contract = new Contract(accountContractId); - this.networkPassphrase = networkPassphrase; this.timeoutSeconds = timeoutSeconds; // Delegate to Stellar SDK's TransactionBuilder diff --git a/packages/core-sdk/src/storage/__tests__/manager.test.ts b/packages/core-sdk/src/storage/__tests__/manager.test.ts index df00aef..766e153 100644 --- a/packages/core-sdk/src/storage/__tests__/manager.test.ts +++ b/packages/core-sdk/src/storage/__tests__/manager.test.ts @@ -1,7 +1,8 @@ import { webcrypto } from 'crypto'; +import type { EncryptedPayload, StorageAdapter } from '../types'; if (!globalThis.crypto) { - // @ts-ignore + // @ts-expect-error Node test environment does not expose writable crypto typing. globalThis.crypto = webcrypto; } if (!globalThis.btoa) { @@ -12,16 +13,16 @@ if (!globalThis.atob) { } import { SecureStorageManager } from '../secure-storage-manager'; -import { StorageAdapter, AccountData, SessionKeysData } from '../types'; +import type { AccountData, SessionKeysData } from '../types'; class MockStorageAdapter implements StorageAdapter { - private store: Map = new Map(); + private store = new Map(); - async get(key: string): Promise { - return this.store.get(key) || null; + async get(key: string): Promise { + return (this.store.get(key) as T | undefined) ?? null; } - async set(key: string, value: any): Promise { + async set(key: string, value: T): Promise { this.store.set(key, value); } @@ -29,7 +30,7 @@ class MockStorageAdapter implements StorageAdapter { this.store.delete(key); } - public inspectStore(): Map { + public inspectStore(): Map { return this.store; } } @@ -51,7 +52,7 @@ describe('SecureStorageManager', () => { await manager.unlock(password); await manager.saveAccount(accountData); - const storedData = await storage.get('account'); + const storedData = await storage.get('account'); expect(storedData).toBeDefined(); // Ensure it's not plaintext diff --git a/packages/core-sdk/src/storage/secure-storage-manager.ts b/packages/core-sdk/src/storage/secure-storage-manager.ts index ad2b210..d93bf92 100644 --- a/packages/core-sdk/src/storage/secure-storage-manager.ts +++ b/packages/core-sdk/src/storage/secure-storage-manager.ts @@ -1,7 +1,10 @@ import { AccountData, EncryptedPayload, SessionKeysData, StorageAdapter } from './types'; -function bufferToBase64(buffer: ArrayBuffer): string { - const bytes = new Uint8Array(buffer); +function bufferToBase64(buffer: BufferSource): string { + const bytes = + buffer instanceof ArrayBuffer + ? new Uint8Array(buffer) + : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); @@ -53,12 +56,12 @@ export class SecureStorageManager { return this.baseKey !== null; } - private async deriveAesKey(salt: Uint8Array | any): Promise { + private async deriveAesKey(salt: BufferSource): Promise { if (!this.baseKey) throw new Error('Storage manager is locked'); return globalThis.crypto.subtle.deriveKey( { name: 'PBKDF2', - salt: salt as any, + salt, iterations: 100000, hash: 'SHA-256', }, @@ -70,8 +73,8 @@ export class SecureStorageManager { } private async encryptData(plaintext: string): Promise { - const salt = globalThis.crypto.getRandomValues(new Uint8Array(16) as any); - const iv = globalThis.crypto.getRandomValues(new Uint8Array(12) as any); + const salt = globalThis.crypto.getRandomValues(new Uint8Array(16)); + const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)); const aesKey = await this.deriveAesKey(salt); const encoder = new TextEncoder(); @@ -94,16 +97,16 @@ export class SecureStorageManager { const iv = base64ToBuffer(payload.iv); const ciphertext = base64ToBuffer(payload.data); - const aesKey = await this.deriveAesKey(new Uint8Array(salt) as any); + const aesKey = await this.deriveAesKey(new Uint8Array(salt)); try { const decryptedBuffer = await globalThis.crypto.subtle.decrypt( - { name: 'AES-GCM', iv: new Uint8Array(iv) as any }, + { name: 'AES-GCM', iv: new Uint8Array(iv) }, aesKey, - ciphertext as any + ciphertext ); return new TextDecoder().decode(decryptedBuffer); - } catch (error: any) { + } catch { throw new Error('Invalid password or corrupted data'); } } @@ -114,7 +117,7 @@ export class SecureStorageManager { } public async getAccount(): Promise { - const payload = (await this.storage.get('account')) as EncryptedPayload | null; + const payload = await this.storage.get('account'); if (!payload) return null; const json = await this.decryptData(payload); return JSON.parse(json); @@ -126,7 +129,7 @@ export class SecureStorageManager { } public async getSessionKeys(): Promise { - const payload = (await this.storage.get('sessionKeys')) as EncryptedPayload | null; + const payload = await this.storage.get('sessionKeys'); if (!payload) return null; const json = await this.decryptData(payload); return JSON.parse(json); diff --git a/packages/core-sdk/src/storage/types.ts b/packages/core-sdk/src/storage/types.ts index d34bdc2..cd55af7 100644 --- a/packages/core-sdk/src/storage/types.ts +++ b/packages/core-sdk/src/storage/types.ts @@ -8,17 +8,17 @@ export interface EncryptedPayload { } export interface StorageAdapter { - get(key: string): Promise; - set(key: string, value: any): Promise; + get(key: string): Promise; + set(key: string, value: T): Promise; remove(key: string): Promise; } export interface AccountData { privateKey: string; - [key: string]: any; + [key: string]: unknown; } export interface SessionKeysData { keys: Record; - [key: string]: any; + [key: string]: unknown; } diff --git a/packages/crypto/eslint.config.cjs b/packages/crypto/eslint.config.cjs index 8f1b1a7..18e4431 100644 --- a/packages/crypto/eslint.config.cjs +++ b/packages/crypto/eslint.config.cjs @@ -24,6 +24,14 @@ module.exports = [ ecmaVersion: 2020, sourceType: 'module', }, + globals: { + Buffer: 'readonly', + Crypto: 'readonly', + CryptoKey: 'readonly', + TextEncoder: 'readonly', + TextDecoder: 'readonly', + crypto: 'readonly', + }, }, plugins: { '@typescript-eslint': tseslint, diff --git a/packages/crypto/src/__tests__/encryption-roundtrip.test.ts b/packages/crypto/src/__tests__/encryption-roundtrip.test.ts index 19c1708..5b764d8 100644 --- a/packages/crypto/src/__tests__/encryption-roundtrip.test.ts +++ b/packages/crypto/src/__tests__/encryption-roundtrip.test.ts @@ -1,9 +1,6 @@ import { describe, expect, it } from '@jest/globals'; -import { - decryptSecretKey, - encryptSecretKey, -} from '../encryption'; +import { decryptSecretKey, encryptSecretKey } from '../encryption'; describe('encryptSecretKey()/decryptSecretKey() round-trip', () => { it('decrypts encrypted payload back to original secret key', async () => { diff --git a/packages/crypto/src/encryption.ts b/packages/crypto/src/encryption.ts index b9f4cb7..1c86086 100644 --- a/packages/crypto/src/encryption.ts +++ b/packages/crypto/src/encryption.ts @@ -1,4 +1,6 @@ import { Buffer } from 'node:buffer'; +import { webcrypto } from 'node:crypto'; +import { TextDecoder, TextEncoder } from 'node:util'; const PBKDF2_ITERATIONS = 100000; const MAX_PBKDF2_ITERATIONS = 600000; @@ -16,7 +18,7 @@ export interface EncryptedSecretKeyPayload { ciphertext: string; } -function getCrypto(): Crypto { +function getCrypto(): webcrypto.Crypto { if (!globalThis.crypto?.subtle) { throw new Error('WebCrypto API is not available in this environment.'); } @@ -37,7 +39,7 @@ async function deriveEncryptionKey( password: string, salt: Uint8Array, iterations: number -): Promise { +): Promise { const cryptoApi = getCrypto(); const passwordKey = await cryptoApi.subtle.importKey( 'raw', @@ -162,11 +164,7 @@ export async function decryptSecretKey( const salt = fromBase64(validatedPayload.salt); const iv = fromBase64(validatedPayload.iv); const ciphertext = fromBase64(validatedPayload.ciphertext); - const encryptionKey = await deriveEncryptionKey( - password, - salt, - validatedPayload.iterations - ); + const encryptionKey = await deriveEncryptionKey(password, salt, validatedPayload.iterations); const plaintext = await cryptoApi.subtle.decrypt( { diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index 7370d23..31ab6c6 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -8,8 +8,5 @@ export const CRYPTO_VERSION = '0.1.0'; export { verifySignature } from './signing'; export { validatePasswordStrength } from './password'; -export { - encryptSecretKey, - decryptSecretKey, -} from './encryption'; +export { encryptSecretKey, decryptSecretKey } from './encryption'; export type { EncryptedSecretKeyPayload } from './encryption'; diff --git a/packages/types/eslint.config.cjs b/packages/types/eslint.config.cjs index 3f919eb..eb22926 100644 --- a/packages/types/eslint.config.cjs +++ b/packages/types/eslint.config.cjs @@ -59,6 +59,7 @@ module.exports = [ afterEach: 'readonly', beforeAll: 'readonly', afterAll: 'readonly', + jest: 'readonly', }, }, plugins: { @@ -67,6 +68,7 @@ module.exports = [ rules: { ...tseslint.configs.recommended.rules, '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'off', }, }, { @@ -77,7 +79,12 @@ module.exports = [ test: 'readonly', it: 'readonly', expect: 'readonly', + beforeEach: 'readonly', + jest: 'readonly', }, }, + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, }, ]; diff --git a/packages/ui-kit/eslint.config.cjs b/packages/ui-kit/eslint.config.cjs index 74b6aad..3afc2dc 100644 --- a/packages/ui-kit/eslint.config.cjs +++ b/packages/ui-kit/eslint.config.cjs @@ -60,6 +60,7 @@ module.exports = [ afterAll: 'readonly', afterEach: 'readonly', jest: 'readonly', + vi: 'readonly', }, }, }, diff --git a/packages/ui-kit/src/__tests__/Form/validation.test.ts b/packages/ui-kit/src/__tests__/Form/validation.test.ts index e8641f2..2a050b5 100644 --- a/packages/ui-kit/src/__tests__/Form/validation.test.ts +++ b/packages/ui-kit/src/__tests__/Form/validation.test.ts @@ -22,7 +22,9 @@ describe('isStellarAddress', () => { }); it('returns false for an address that does not start with G', () => { - expect(isStellarAddress('SDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37')).toBe(false); + expect(isStellarAddress('SDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37')).toBe( + false + ); }); it('returns false for an address that is too short', () => { diff --git a/packages/ui-kit/src/components/Form/AddressInput.tsx b/packages/ui-kit/src/components/Form/AddressInput.tsx index a2060a5..a2e096b 100644 --- a/packages/ui-kit/src/components/Form/AddressInput.tsx +++ b/packages/ui-kit/src/components/Form/AddressInput.tsx @@ -10,7 +10,6 @@ import { cn } from '@/lib/utils'; /** Safely attempt to read the react-hook-form context without throwing. */ function useOptionalFormContext() { try { - // eslint-disable-next-line react-hooks/rules-of-hooks return useFormContext(); } catch { return null; @@ -21,8 +20,10 @@ function useOptionalFormContext() { // AddressInputBase – the pure presentational layer // --------------------------------------------------------------------------- -export interface AddressInputBaseProps - extends Omit, 'type'> { +export interface AddressInputBaseProps extends Omit< + React.InputHTMLAttributes, + 'type' +> { /** Field label */ label?: string; /** Validation error message */ @@ -58,11 +59,7 @@ const AddressInputBase = React.forwardRef {error && ( - )} diff --git a/packages/ui-kit/src/components/Form/AmountInput.tsx b/packages/ui-kit/src/components/Form/AmountInput.tsx index f4ff0ae..3131803 100644 --- a/packages/ui-kit/src/components/Form/AmountInput.tsx +++ b/packages/ui-kit/src/components/Form/AmountInput.tsx @@ -10,7 +10,6 @@ import { cn } from '@/lib/utils'; function useOptionalFormContext() { try { - // eslint-disable-next-line react-hooks/rules-of-hooks return useFormContext(); } catch { return null; @@ -21,8 +20,10 @@ function useOptionalFormContext() { // AmountInputBase – pure presentational layer // --------------------------------------------------------------------------- -export interface AmountInputBaseProps - extends Omit, 'type'> { +export interface AmountInputBaseProps extends Omit< + React.InputHTMLAttributes, + 'type' +> { /** Field label */ label?: string; /** Validation or runtime error message */ @@ -37,17 +38,7 @@ export interface AmountInputBaseProps const AmountInputBase = React.forwardRef( ( - { - label = 'Amount', - error, - balance, - asset = 'XLM', - onMax, - className, - id, - onChange, - ...props - }, + { label = 'Amount', error, balance, asset = 'XLM', onMax, className, id, onChange, ...props }, ref ) => { const inputId = id ?? label.toLowerCase().replace(/\s+/g, '-'); diff --git a/packages/ui-kit/src/components/Form/Form.stories.tsx b/packages/ui-kit/src/components/Form/Form.stories.tsx index e1e749e..077fb8a 100644 --- a/packages/ui-kit/src/components/Form/Form.stories.tsx +++ b/packages/ui-kit/src/components/Form/Form.stories.tsx @@ -50,22 +50,9 @@ export const SendTransaction: Story = { return (
- - - + + + Send Transaction diff --git a/packages/ui-kit/src/components/Form/Form.tsx b/packages/ui-kit/src/components/Form/Form.tsx index f8229d7..b3e5eb1 100644 --- a/packages/ui-kit/src/components/Form/Form.tsx +++ b/packages/ui-kit/src/components/Form/Form.tsx @@ -42,8 +42,7 @@ FormError.displayName = 'FormError'; // FormSubmit – submit button that reads loading state from form context // --------------------------------------------------------------------------- -export interface FormSubmitProps - extends React.ButtonHTMLAttributes { +export interface FormSubmitProps extends React.ButtonHTMLAttributes { /** Override the auto-derived isSubmitting state */ loading?: boolean; children: React.ReactNode; diff --git a/packages/ui-kit/src/components/Form/PasswordInput.tsx b/packages/ui-kit/src/components/Form/PasswordInput.tsx index d081aca..2cb5842 100644 --- a/packages/ui-kit/src/components/Form/PasswordInput.tsx +++ b/packages/ui-kit/src/components/Form/PasswordInput.tsx @@ -11,7 +11,6 @@ import { getPasswordStrength } from './validation'; function useOptionalFormContext() { try { - // eslint-disable-next-line react-hooks/rules-of-hooks return useFormContext(); } catch { return null; @@ -50,9 +49,7 @@ function StrengthMeter({ password, id }: StrengthMeterProps) {
{/* Label */} -

- {strength.label} -

+

{strength.label}

); } @@ -61,8 +58,10 @@ function StrengthMeter({ password, id }: StrengthMeterProps) { // PasswordInputBase – pure presentational layer // --------------------------------------------------------------------------- -export interface PasswordInputBaseProps - extends Omit, 'type'> { +export interface PasswordInputBaseProps extends Omit< + React.InputHTMLAttributes, + 'type' +> { /** Field label */ label?: string; /** Validation error message */ @@ -101,10 +100,7 @@ const PasswordInputBase = React.forwardRef @@ -114,11 +110,7 @@ const PasswordInputBase = React.forwardRef - {visible ? ( - - ) : ( - - )} + {visible ? : } @@ -127,11 +119,7 @@ const PasswordInputBase = React.forwardRef + )} diff --git a/packages/ui-kit/src/components/Form/validation.ts b/packages/ui-kit/src/components/Form/validation.ts index 6deb6fe..1deb51d 100644 --- a/packages/ui-kit/src/components/Form/validation.ts +++ b/packages/ui-kit/src/components/Form/validation.ts @@ -97,7 +97,13 @@ const STRENGTH_BG_CLASSES: string[] = [ */ export function getPasswordStrength(password: string): PasswordStrength & { bgClass: string } { if (!password) { - return { score: 0, label: 'Very Weak', colorClass: STRENGTH_COLOR_CLASSES[0], bgClass: STRENGTH_BG_CLASSES[0], percent: 0 }; + return { + score: 0, + label: 'Very Weak', + colorClass: STRENGTH_COLOR_CLASSES[0], + bgClass: STRENGTH_BG_CLASSES[0], + percent: 0, + }; } let score = 0; @@ -110,7 +116,11 @@ export function getPasswordStrength(password: string): PasswordStrength & { bgCl // Penalise trivial patterns if (/^(.)\1+$/.test(password)) score = Math.max(0, score - 2); - if (/^(012|123|234|345|456|567|678|789|890|abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz)/i.test(password)) { + if ( + /^(012|123|234|345|456|567|678|789|890|abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz)/i.test( + password + ) + ) { score = Math.max(0, score - 1); } diff --git a/packages/ui-kit/src/index.ts b/packages/ui-kit/src/index.ts index 277fc94..e45f9d5 100644 --- a/packages/ui-kit/src/index.ts +++ b/packages/ui-kit/src/index.ts @@ -40,10 +40,7 @@ export type { FormProps, FormSubmitProps, FormErrorProps } from './components/Fo export { AddressInput, AddressInputBase } from './components/Form/AddressInput'; export type { AddressInputProps, AddressInputBaseProps } from './components/Form/AddressInput'; -export { - AmountInput as FormAmountInput, - AmountInputBase, -} from './components/Form/AmountInput'; +export { AmountInput as FormAmountInput, AmountInputBase } from './components/Form/AmountInput'; export type { AmountInputProps as FormAmountInputProps, AmountInputBaseProps, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 034338c..21c669d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -82,10 +82,10 @@ importers: version: 18.3.7(@types/react@18.3.28) '@typescript-eslint/eslint-plugin': specifier: ^8.0.0 - version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^8.0.0 - version: 8.56.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + version: 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) '@vitejs/plugin-react': specifier: ^4.2.0 version: 4.7.0(vite@5.4.21(@types/node@25.3.1)) @@ -93,14 +93,14 @@ importers: specifier: ^10.4.0 version: 10.4.27(postcss@8.5.6) eslint: - specifier: ^10.1.0 - version: 10.1.0(jiti@1.21.7) + specifier: ^9.0.0 + version: 9.39.3(jiti@1.21.7) eslint-plugin-react: specifier: ^7.37.0 - version: 7.37.5(eslint@10.1.0(jiti@1.21.7)) + version: 7.37.5(eslint@9.39.3(jiti@1.21.7)) eslint-plugin-react-hooks: specifier: ^5.0.0 - version: 5.2.0(eslint@10.1.0(jiti@1.21.7)) + version: 5.2.0(eslint@9.39.3(jiti@1.21.7)) globals: specifier: ^17.4.0 version: 17.4.0 @@ -119,36 +119,6 @@ importers: typescript: specifier: ^5.6.0 version: 5.9.3 - vite: - specifier: ^5.0.0 - version: 5.4.21(@types/node@25.3.1) - '@vitejs/plugin-react': - specifier: ^4.2.0 - version: 4.7.0(vite@5.4.21(@types/node@25.3.1)) - autoprefixer: - specifier: ^10.4.0 - version: 10.4.27(postcss@8.5.6) - eslint: - specifier: ^9.0.0 - version: 9.39.3(jiti@1.21.7) - eslint-plugin-react: - specifier: ^7.34.0 - version: 7.37.5(eslint@9.39.3(jiti@1.21.7)) - eslint-plugin-react-hooks: - specifier: ^4.6.0 - version: 4.6.2(eslint@9.39.3(jiti@1.21.7)) - globals: - specifier: ^15.0.0 - version: 15.15.0 - jsdom: - specifier: ^23.0.0 - version: 23.2.0 - tailwindcss: - specifier: ^3.4.0 - version: 3.4.19(yaml@2.8.2) - typescript: - specifier: ^5.6.0 - version: 5.9.3 vite: specifier: ^5.4.0 version: 5.4.21(@types/node@25.3.1) @@ -367,6 +337,9 @@ importers: packages/ui-kit: dependencies: + '@hookform/resolvers': + specifier: ^3.3.0 + version: 3.10.0(react-hook-form@7.72.0(react@18.3.1)) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.2.4(@types/react@18.3.28)(react@18.3.1) @@ -379,9 +352,15 @@ importers: lucide-react: specifier: ^0.344.0 version: 0.344.0(react@18.3.1) + react-hook-form: + specifier: ^7.49.0 + version: 7.72.0(react@18.3.1) tailwind-merge: specifier: ^2.2.0 version: 2.6.1 + zod: + specifier: ^3.22.0 + version: 3.25.76 devDependencies: '@eslint/js': specifier: ^9.0.0 @@ -1342,26 +1321,14 @@ packages: resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-array@0.23.3': - resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.4.2': resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.5.3': - resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@0.17.0': resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@1.1.1': - resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.4': resolution: {integrity: sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1374,18 +1341,10 @@ packages: resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@3.0.3': - resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/plugin-kit@0.4.1': resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.6.1': - resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@fal-works/esbuild-plugin-global-externals@2.1.2': resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} @@ -1404,6 +1363,11 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@hookform/resolvers@3.10.0': + resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} + peerDependencies: + react-hook-form: ^7.0.0 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -2686,9 +2650,6 @@ packages: '@types/escodegen@0.0.6': resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==} - '@types/esrecurse@4.3.1': - resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} - '@types/estree@0.0.51': resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} @@ -2818,17 +2779,6 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@7.18.0': - resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/eslint-plugin@8.56.1': resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2837,16 +2787,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@7.18.0': - resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/parser@8.56.1': resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2860,10 +2800,6 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@7.18.0': - resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/scope-manager@8.56.1': resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2874,16 +2810,6 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@7.18.0': - resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/type-utils@8.56.1': resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2891,35 +2817,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@7.18.0': - resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/types@8.56.1': resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@7.18.0': - resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/typescript-estree@8.56.1': resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@7.18.0': - resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - '@typescript-eslint/utils@8.56.1': resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2927,10 +2834,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@7.18.0': - resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/visitor-keys@8.56.1': resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4001,12 +3904,6 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-plugin-react-hooks@4.6.2: - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - eslint-plugin-react-hooks@5.2.0: resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} engines: {node: '>=10'} @@ -4023,10 +3920,6 @@ packages: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-scope@9.1.2: - resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - 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} @@ -4039,16 +3932,6 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.1.0: - resolution: {integrity: sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - eslint@9.39.3: resolution: {integrity: sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4063,10 +3946,6 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - espree@11.2.0: - resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -4394,8 +4273,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.15.0: - resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + globals@17.4.0: + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} engines: {node: '>=18'} globalthis@1.0.4: @@ -4417,9 +4296,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - gunzip-maybe@1.4.2: resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} hasBin: true @@ -5930,6 +5806,12 @@ packages: peerDependencies: react: ^18.0.0 || ^19.0.0 + react-hook-form@7.72.0: + resolution: {integrity: sha512-V4v6jubaf6JAurEaVnT9aUPKFbNtDgohj5CIgVGyPHvT9wRx5OZHVjz31GsxnPNI278XMu+ruFz+wGOscHaLKw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -6571,12 +6453,6 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -8066,11 +7942,6 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.1.0(jiti@1.21.7))': - dependencies: - eslint: 10.1.0(jiti@1.21.7) - eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.3(jiti@1.21.7))': dependencies: eslint: 9.39.3(jiti@1.21.7) @@ -8086,30 +7957,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-array@0.23.3': - dependencies: - '@eslint/object-schema': 3.0.3 - debug: 4.4.3 - minimatch: 10.2.4 - transitivePeerDependencies: - - supports-color - '@eslint/config-helpers@0.4.2': dependencies: '@eslint/core': 0.17.0 - '@eslint/config-helpers@0.5.3': - dependencies: - '@eslint/core': 1.1.1 - '@eslint/core@0.17.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/core@1.1.1': - dependencies: - '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.4': dependencies: ajv: 6.14.0 @@ -8128,18 +7983,11 @@ snapshots: '@eslint/object-schema@2.1.7': {} - '@eslint/object-schema@3.0.3': {} - '@eslint/plugin-kit@0.4.1': dependencies: '@eslint/core': 0.17.0 levn: 0.4.1 - '@eslint/plugin-kit@0.6.1': - dependencies: - '@eslint/core': 1.1.1 - levn: 0.4.1 - '@fal-works/esbuild-plugin-global-externals@2.1.2': {} '@floating-ui/core@1.7.4': @@ -8159,6 +8007,10 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@hookform/resolvers@3.10.0(react-hook-form@7.72.0(react@18.3.1))': + dependencies: + react-hook-form: 7.72.0(react@18.3.1) + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -9984,8 +9836,6 @@ snapshots: '@types/escodegen@0.0.6': {} - '@types/esrecurse@4.3.1': {} - '@types/estree@0.0.51': {} '@types/estree@1.0.8': {} @@ -10126,24 +9976,6 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 7.18.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 7.18.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 9.39.3(jiti@1.21.7) - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -10160,19 +9992,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.3 - eslint: 9.39.3(jiti@1.21.7) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.56.1 @@ -10194,11 +10013,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.18.0': - dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/scope-manager@8.56.1': dependencies: '@typescript-eslint/types': 8.56.1 @@ -10208,18 +10022,6 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@7.18.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) - '@typescript-eslint/utils': 7.18.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) - debug: 4.4.3 - eslint: 9.39.3(jiti@1.21.7) - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/type-utils@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.56.1 @@ -10232,25 +10034,8 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/types@8.56.1': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.3 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.8 - semver: 7.7.4 - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) @@ -10266,17 +10051,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@1.21.7)) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) - eslint: 9.39.3(jiti@1.21.7) - transitivePeerDependencies: - - supports-color - - typescript - '@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@1.21.7)) @@ -10288,11 +10062,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@7.18.0': - dependencies: - '@typescript-eslint/types': 7.18.0 - eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@8.56.1': dependencies: '@typescript-eslint/types': 8.56.1 @@ -11528,36 +11297,10 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-plugin-react-hooks@5.2.0(eslint@10.1.0(jiti@1.21.7)): - dependencies: - eslint: 10.1.0(jiti@1.21.7) - eslint-plugin-react-hooks@5.2.0(eslint@9.39.3(jiti@1.21.7)): dependencies: eslint: 9.39.3(jiti@1.21.7) - eslint-plugin-react@7.37.5(eslint@10.1.0(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: 10.1.0(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-react@7.37.5(eslint@9.39.3(jiti@1.21.7)): dependencies: array-includes: 3.1.9 @@ -11585,56 +11328,12 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@9.1.2: - dependencies: - '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 - 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@10.1.0(jiti@1.21.7): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.3 - '@eslint/config-helpers': 0.5.3 - '@eslint/core': 1.1.1 - '@eslint/plugin-kit': 0.6.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 - cross-spawn: 7.0.6 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint-scope: 9.1.2 - eslint-visitor-keys: 5.0.1 - espree: 11.2.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 - minimatch: 10.2.4 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 1.21.7 - transitivePeerDependencies: - - supports-color - eslint@9.39.3(jiti@1.21.7): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@1.21.7)) @@ -11682,12 +11381,6 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 4.2.1 - espree@11.2.0: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 5.0.1 - esprima@4.0.1: {} esquery@1.7.0: @@ -12107,8 +11800,6 @@ snapshots: graceful-fs@4.2.11: {} - graphemer@1.4.0: {} - gunzip-maybe@1.4.2: dependencies: browserify-zlib: 0.1.4 @@ -14080,6 +13771,10 @@ snapshots: dependencies: react: 18.3.1 + react-hook-form@7.72.0(react@18.3.1): + dependencies: + react: 18.3.1 + react-is@16.13.1: {} react-is@17.0.2: {} @@ -14861,10 +14556,6 @@ snapshots: tree-kill@1.2.2: {} - ts-api-utils@1.4.3(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3