diff --git a/.changeset/mighty-stamps-build.md b/.changeset/mighty-stamps-build.md new file mode 100644 index 000000000..87b01b28b --- /dev/null +++ b/.changeset/mighty-stamps-build.md @@ -0,0 +1,7 @@ +--- +'@ton/walletkit': patch +'@ton/appkit': patch +'@ton/appkit-react': patch +--- + +Added support for new TonConnect features: SignMessage, StructeredItems, EmbeddedRequests diff --git a/apps/demo-wallet/package.json b/apps/demo-wallet/package.json index 343acb3dd..81f4d936f 100644 --- a/apps/demo-wallet/package.json +++ b/apps/demo-wallet/package.json @@ -74,4 +74,4 @@ "vite-bundle-analyzer": "^1.3.7", "webextension-polyfill": "^0.12.0" } -} +} \ No newline at end of file diff --git a/apps/demo-wallet/src/components/AppRouter.tsx b/apps/demo-wallet/src/components/AppRouter.tsx index 632f166de..57020400a 100644 --- a/apps/demo-wallet/src/components/AppRouter.tsx +++ b/apps/demo-wallet/src/components/AppRouter.tsx @@ -22,6 +22,7 @@ import { TransactionDetail, Swap, Staking, + TonConnectRoute, } from '../pages'; import { useWalletDataUpdater } from '@/hooks/useWalletDataUpdater'; @@ -144,6 +145,14 @@ export const AppRouter: React.FC = () => { } /> + + + + } + /> {/* Redirect root to appropriate route */} } /> diff --git a/apps/demo-wallet/src/components/ConnectRequestModal.tsx b/apps/demo-wallet/src/components/ConnectRequestModal.tsx index 4748dad25..fa681efe9 100644 --- a/apps/demo-wallet/src/components/ConnectRequestModal.tsx +++ b/apps/demo-wallet/src/components/ConnectRequestModal.tsx @@ -13,7 +13,6 @@ import type { SavedWallet } from '@demo/wallet-core'; import { toast } from 'sonner'; import { Button } from './Button'; -import { Card } from './Card'; import { DAppInfo } from './DAppInfo'; import { WalletPreview } from './WalletPreview'; import { createComponentLogger } from '../utils/logger'; @@ -114,9 +113,10 @@ export const ConnectRequestModal: React.FC = ({ if (!isOpen) return null; return ( -
-
- +
+
+ {/* Scrollable content */} +
{/* Header */}
@@ -289,30 +289,30 @@ export const ConnectRequestModal: React.FC = ({
- - {/* Action Buttons */} -
- - -
- +
+ + {/* Action Buttons — always visible at bottom */} +
+ + +
); diff --git a/apps/demo-wallet/src/components/JettonFlow.tsx b/apps/demo-wallet/src/components/JettonFlow.tsx new file mode 100644 index 000000000..b16a7785d --- /dev/null +++ b/apps/demo-wallet/src/components/JettonFlow.tsx @@ -0,0 +1,101 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { memo } from 'react'; +import type { Address } from '@ton/core'; +import type { TransactionTraceMoneyFlowItem } from '@ton/walletkit'; + +import { resolveTokenAddress, TON_INFO, useJettonInfo } from '../hooks/useJettonInfo'; +import { formatUnits } from '../utils/units'; + +export const JettonNameDisplay = memo(function JettonNameDisplay({ + jettonAddress, +}: { + jettonAddress: Address | string | undefined; +}) { + const jettonInfo = useJettonInfo(resolveTokenAddress(jettonAddress)); + const name = jettonInfo?.name; + return
{name ?? jettonAddress?.toString() ?? 'UNKNOWN'}
; +}); + +export const JettonAmountDisplay = memo(function JettonAmountDisplay({ + amount, + jettonAddress, +}: { + amount: bigint; + jettonAddress: Address | string | undefined; +}) { + const jettonInfo = useJettonInfo(resolveTokenAddress(jettonAddress)); + const decimals = jettonInfo?.decimals ?? 9; + const symbol = jettonInfo?.symbol ?? 'UNKWN'; + return ( +
+ {formatUnits(amount, decimals)} {symbol} +
+ ); +}); + +export const JettonImage = memo(function JettonImage({ + jettonAddress, +}: { + jettonAddress: Address | string | undefined; +}) { + const jettonInfo = useJettonInfo(resolveTokenAddress(jettonAddress)); + if (!jettonInfo?.image) { + return {TON_INFO.name}; + } + return {jettonInfo.name}; +}); + +const JettonFlowItem = memo(function JettonFlowItem({ + jettonAddress, + amount, +}: { + jettonAddress: Address | string | undefined; + amount: string; +}) { + return ( +
+ + + + +
= 0n ? 'text-green-600' : 'text-red-600'}`}> + {BigInt(amount) >= 0n ? '+' : ''} + +
+
+ ); +}); + +export const JettonFlow = memo(function JettonFlow({ transfers }: { transfers: TransactionTraceMoneyFlowItem[] }) { + return ( +
+
Money Flow:
+
+ {transfers?.length > 0 + ? transfers.map((transfer) => + transfer.assetType === 'jetton' ? ( + + ) : ( + + ), + ) + : null} +
+
+ ); +}); diff --git a/apps/demo-wallet/src/components/Layout.tsx b/apps/demo-wallet/src/components/Layout.tsx index 84c9445b9..8fc99b049 100644 --- a/apps/demo-wallet/src/components/Layout.tsx +++ b/apps/demo-wallet/src/components/Layout.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { SettingsDropdown } from './SettingsDropdown'; -import { StreamingStatus } from './StreamingStatus'; +// import { StreamingStatus } from './StreamingStatus'; interface LayoutProps { children: React.ReactNode; @@ -53,7 +53,7 @@ export const Layout: React.FC = ({ {headerAction} {showLogout && ( <> - + {/* */} )} diff --git a/apps/demo-wallet/src/components/RecentTransactions.tsx b/apps/demo-wallet/src/components/RecentTransactions.tsx index bc26a4b78..d848260dc 100644 --- a/apps/demo-wallet/src/components/RecentTransactions.tsx +++ b/apps/demo-wallet/src/components/RecentTransactions.tsx @@ -13,7 +13,7 @@ import { useWalletStore } from '@demo/wallet-core'; import { Base64ToHex } from '@ton/walletkit'; import type { Event, Action } from '@ton/walletkit'; -import { formatTonForDisplay, sameAddress } from '../utils'; +import { formatTonForDisplay, getTonviewerTxUrl, sameAddress } from '../utils'; import { TraceRow } from './TraceRow'; import { TransactionErrorState, TransactionLoadingState, TransactionEmptyState, ActionCard } from './transactions'; @@ -26,14 +26,20 @@ interface RecentTransactionsProps { * Displays a list of recent blockchain transactions for the current wallet */ export const RecentTransactions: React.FC = memo(({ embedded = false }) => { - const { events, loadEvents, address, hasNextEvents, pendingTransactions } = useWalletStore( - useShallow((state) => ({ - events: state.walletManagement.events, - loadEvents: state.loadEvents, - address: state.walletManagement.address, - hasNextEvents: state.walletManagement.hasNextEvents, - pendingTransactions: state.walletManagement.pendingTransactions, - })), + const { events, loadEvents, address, hasNextEvents, pendingTransactions, network } = useWalletStore( + useShallow((state) => { + const activeWallet = state.walletManagement.savedWallets.find( + (w) => w.id === state.walletManagement.activeWalletId, + ); + return { + events: state.walletManagement.events, + loadEvents: state.loadEvents, + address: state.walletManagement.address, + hasNextEvents: state.walletManagement.hasNextEvents, + pendingTransactions: state.walletManagement.pendingTransactions, + network: activeWallet?.network || 'testnet', + }; + }), ); const [isInitialLoading, setIsInitialLoading] = useState(true); const [isPaginating, setIsPaginating] = useState(false); @@ -249,6 +255,7 @@ export const RecentTransactions: React.FC = memo(({ emb const isPending = p.finality !== 'confirmed' && p.finality !== 'finalized'; const finality = p.finality ?? 'pending'; + const pendingExplorerHash = p.externalHash ?? p.traceId; if (p.action) { return ( @@ -259,7 +266,7 @@ export const RecentTransactions: React.FC = memo(({ emb traceLink={ isPending ? undefined - : `/wallet/trace/${p.traceId}${p.externalHash ? ':' + p.externalHash : ''}` + : getTonviewerTxUrl(network, pendingExplorerHash) } isPending={isPending} finality={finality} @@ -317,9 +324,7 @@ export const RecentTransactions: React.FC = memo(({ emb myAddress={address || ''} timestamp={preview?.timestamp ?? Math.floor(Date.now() / 1000)} traceLink={ - isPending - ? undefined - : `/wallet/trace/${p.traceId}${p.externalHash ? ':' + p.externalHash : ''}` + isPending ? undefined : getTonviewerTxUrl(network, pendingExplorerHash) } isPending={isPending} finality={finality} @@ -374,7 +379,7 @@ export const RecentTransactions: React.FC = memo(({ emb action={relevantAction} myAddress={address || ''} timestamp={ev.timestamp} - traceLink={`/wallet/trace/${traceId}`} + traceLink={getTonviewerTxUrl(network, externalHash)} finality="done" debugId={`event-${traceId.slice(0, 12)}`} /> diff --git a/apps/demo-wallet/src/components/RequestModal.tsx b/apps/demo-wallet/src/components/RequestModal.tsx new file mode 100644 index 000000000..0045cccca --- /dev/null +++ b/apps/demo-wallet/src/components/RequestModal.tsx @@ -0,0 +1,342 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React, { useEffect, useMemo, useState } from 'react'; +import type { SendTransactionRequestEvent, SignMessageRequestEvent, TransactionEmulatedPreview } from '@ton/walletkit'; +import { useAuth, useWalletKit, useWalletStore } from '@demo/wallet-core'; +import type { SavedWallet } from '@demo/wallet-core'; +import { toast } from 'sonner'; + +import { Button } from './Button'; +import { HoldToSignButton } from './HoldToSignButton'; +import { JettonFlow } from './JettonFlow'; +import { SuccessCard } from './SuccessCard'; +import { createComponentLogger } from '../utils/logger'; + +type RequestEvent = SendTransactionRequestEvent | SignMessageRequestEvent; +type WarningTone = 'red' | 'yellow'; + +interface RequestModalProps { + request: RequestEvent; + savedWallets: SavedWallet[]; + isOpen: boolean; + title: string; + subtitle: string; + details?: React.ReactNode; + warning: { tone: WarningTone; message: React.ReactNode }; + approveLabel: string; + successMessage: string; + testIds: { request: string; approve: string; reject: string }; + onApprove: () => Promise; + onReject: () => void; + loggerName: string; + previewMode: 'send' | 'sign'; +} + +const WARNING_CLASSES: Record = { + red: { + container: 'bg-red-50 border-red-200', + icon: 'text-red-400', + text: 'text-red-800', + }, + yellow: { + container: 'bg-yellow-50 border-yellow-200', + icon: 'text-yellow-400', + text: 'text-yellow-800', + }, +}; + +export const RequestModal: React.FC = ({ + request, + savedWallets, + isOpen, + title, + subtitle, + details, + warning, + approveLabel, + successMessage, + testIds, + onApprove, + onReject, + loggerName, + previewMode, +}) => { + const walletKit = useWalletKit(); + const isAuthenticated = useWalletStore((state) => state.walletManagement.isAuthenticated); + const { holdToSign } = useAuth(); + const [isLoading, setIsLoading] = useState(false); + const [showSuccess, setShowSuccess] = useState(false); + const [isExpired, setIsExpired] = useState(false); + const [localPreview, setLocalPreview] = useState(undefined); + + const log = useMemo(() => createComponentLogger(loggerName), [loggerName]); + + const currentWallet = useMemo(() => { + if (!request.walletAddress) return null; + return savedWallets.find((wallet) => wallet.kitWalletId === request.walletId) || null; + }, [savedWallets, request.walletAddress, request.walletId]); + + useEffect(() => { + const checkExpiration = () => { + const validUntil = request.request?.validUntil; + if (validUntil) { + const now = Math.floor(Date.now() / 1000); + setIsExpired(validUntil < now); + } else { + setIsExpired(false); + } + }; + checkExpiration(); + const interval = setInterval(checkExpiration, 1000); + return () => clearInterval(interval); + }, [request.request?.validUntil]); + + useEffect(() => { + if (!isAuthenticated) return; + async function updatePreview() { + if (request.preview.data) return; + await walletKit?.ensureInitialized(); + const preview = await walletKit + ?.getWallet(request.walletId ?? '') + ?.getTransactionPreview(request.request, { mode: previewMode }); + setLocalPreview(preview); + } + updatePreview(); + }, [request.walletId, request.request, request.preview, walletKit, isAuthenticated, previewMode]); + + const preview = useMemo(() => localPreview ?? request.preview.data, [request, localPreview]); + + useEffect(() => { + if (!isOpen) { + setShowSuccess(false); + setIsLoading(false); + setIsExpired(false); + } + }, [isOpen]); + + const handleApprove = async () => { + setIsLoading(true); + try { + await onApprove(); + setShowSuccess(true); + } catch (error) { + log.error(`Failed to approve ${loggerName}:`, error); + toast.error('Failed to approve request', { + description: (error as Error)?.message, + }); + } finally { + setIsLoading(false); + } + }; + + if (!isOpen) return null; + if (showSuccess) return ; + + const warningClasses = WARNING_CLASSES[warning.tone]; + + let dAppHost: string | undefined; + try { + dAppHost = request.dAppInfo?.url ? new URL(request.dAppInfo.url).host : undefined; + } catch (_e) { + dAppHost = request.dAppInfo?.url; + } + + return ( +
+
+ {/* Scrollable content */} +
+
+
+

+ {title} +

+

{subtitle}

+
+ + {/* Combined dApp + Wallet block */} +
+ {/* dApp row */} +
+ {request.dAppInfo?.iconUrl ? ( + {request.dAppInfo.name} { + e.currentTarget.style.display = 'none'; + }} + /> + ) : ( +
+ + + +
+ )} +
+

+ {request.dAppInfo?.name || 'Unknown dApp'} +

+ {dAppHost &&

{dAppHost}

} +
+
+ + {/* Wallet row */} + {currentWallet && ( + <> +
+
+
+ + + +
+
+

+ {currentWallet.name} +

+

+ {currentWallet.address.slice(0, 6)}...{currentWallet.address.slice(-6)} +

+
+
+ + )} +
+ + {isExpired ? ( +
+
+
+ + + +
+
+

Transaction Expired

+

+ This transaction request has expired and can no longer be signed. Please + reject it and request a new transaction from the dApp. +

+
+
+
+ ) : ( + <> + {details} + + {preview && preview.result === 'success' && ( +
+
+ {preview.moneyFlow?.outputs === '0' && + preview.moneyFlow?.inputs === '0' && + preview.moneyFlow?.ourTransfers.length === 0 ? ( +
+

+ This transaction doesn't involve any token transfers +

+
+ ) : ( + + )} +
+
+ )} + + {preview && (preview.result === 'failure' || preview.error) && ( +
+

+ Error: {preview.error?.message} +

+
+ )} + +
+
+
+ + + +
+
+

{warning.message}

+
+
+
+ + )} +
+
+ + {/* Action buttons — always visible at bottom */} +
+ + {!isExpired && + (holdToSign ? ( + + ) : ( + + ))} +
+
+
+ ); +}; diff --git a/apps/demo-wallet/src/components/SignMessageRequestModal.tsx b/apps/demo-wallet/src/components/SignMessageRequestModal.tsx new file mode 100644 index 000000000..65826a1bc --- /dev/null +++ b/apps/demo-wallet/src/components/SignMessageRequestModal.tsx @@ -0,0 +1,64 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React from 'react'; +import type { SignMessageRequestEvent } from '@ton/walletkit'; +import type { SavedWallet } from '@demo/wallet-core'; +import { useSignMessageRequests } from '@demo/wallet-core'; + +import { RequestModal } from './RequestModal'; +import { TransactionRequestDetails } from './TransactionRequestDetails'; + +interface SignMessageRequestModalProps { + request: SignMessageRequestEvent; + savedWallets: SavedWallet[]; + isOpen: boolean; +} + +export const SignMessageRequestModal: React.FC = ({ request, savedWallets, isOpen }) => { + const { approveSignMessageRequest, rejectSignMessageRequest } = useSignMessageRequests(); + + const handleApprove = async () => { + await approveSignMessageRequest(); + }; + + const handleReject = () => { + rejectSignMessageRequest('User rejected the sign message request'); + }; + + return ( + } + warning={{ + tone: 'yellow', + message: ( + <> + Warning: This will sign a transaction that the dApp can submit later. Only + approve if you trust the requesting dApp. + + ), + }} + approveLabel="Sign Message" + successMessage="Message signed successfully" + testIds={{ + request: 'request', + approve: 'sign-message-approve', + reject: 'sign-message-reject', + }} + onApprove={handleApprove} + onReject={handleReject} + loggerName="SignMessageRequestModal" + previewMode="sign" + /> + ); +}; diff --git a/apps/demo-wallet/src/components/SuccessCard.tsx b/apps/demo-wallet/src/components/SuccessCard.tsx new file mode 100644 index 000000000..d83f57625 --- /dev/null +++ b/apps/demo-wallet/src/components/SuccessCard.tsx @@ -0,0 +1,40 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React from 'react'; + +interface SuccessCardProps { + message: string; +} + +export const SuccessCard: React.FC = ({ message }) => ( +
+ +
+
+
+
+ + + +
+
+
+

Success!

+

{message}

+
+
+
+
+); diff --git a/apps/demo-wallet/src/components/TraceRow.tsx b/apps/demo-wallet/src/components/TraceRow.tsx index 5979c36de..f1050210d 100644 --- a/apps/demo-wallet/src/components/TraceRow.tsx +++ b/apps/demo-wallet/src/components/TraceRow.tsx @@ -9,10 +9,10 @@ import React, { memo, useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { useShallow } from 'zustand/react/shallow'; -import { Base64ToHex } from '@ton/walletkit'; import type { ToncenterTraceItem } from '@ton/walletkit'; import { useWalletKit, useWalletStore, getChainNetwork } from '@demo/wallet-core'; +import { getTonviewerTxUrl } from '@/utils'; import { log } from '@/utils/logger'; // Local type definitions for transaction data @@ -388,8 +388,10 @@ export const TraceRow: React.FC = memo( if (error || !trace) { return ( -
@@ -408,7 +410,7 @@ export const TraceRow: React.FC = memo(

{error}

- + ); } @@ -416,12 +418,7 @@ export const TraceRow: React.FC = memo( const transactionCount = Object.keys(trace.transactions).length; const actionCount = trace.actions?.length ?? 0; - // Determine the link destination - use first transaction's hash - const firstTxHash = trace.transactions_order?.[0] || Object.keys(trace.transactions)[0]; - const firstTx = trace.transactions[firstTxHash]; - const linkTo = firstTx?.in_msg?.hash - ? `/wallet/trace/${Base64ToHex(firstTx.in_msg.hash)}` - : `/wallet/trace/${traceId}`; + const linkTo = getTonviewerTxUrl(walletNetwork, externalHash ?? traceId); const orderedTransactions = trace.transactions_order ? trace.transactions_order.map((hash) => trace.transactions[hash] as TransactionData) @@ -430,8 +427,10 @@ export const TraceRow: React.FC = memo( return (
{/* Main trace header - clickable for navigation */} -
@@ -545,7 +544,7 @@ export const TraceRow: React.FC = memo(

- + {/* Expand/Collapse button for transaction previews */} {transactionCount > 1 && ( diff --git a/apps/demo-wallet/src/components/TransactionRequestDetails.tsx b/apps/demo-wallet/src/components/TransactionRequestDetails.tsx new file mode 100644 index 000000000..98b4d416e --- /dev/null +++ b/apps/demo-wallet/src/components/TransactionRequestDetails.tsx @@ -0,0 +1,214 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React from 'react'; +import type { StructuredItem, TransactionRequest, TransactionRequestMessage } from '@ton/walletkit'; +import { getAddressExplorerUrls } from '@demo/wallet-core'; + +import { useActiveWalletNetwork, useJettonInfo } from '../hooks/useJettonInfo'; +import { normalizeAddress, shortenAddress } from '../utils/formatters'; +import { decodeTextCommentPayload } from '../utils/payload'; +import { formatNanoTonAmount, formatTokenAmount } from '../utils/units'; + +interface TransactionRequestDetailsProps { + request: TransactionRequest; + title?: string; +} + +function AddressLink({ address, label }: { address?: string; label?: string }) { + const network = useActiveWalletNetwork(); + if (!address) return null; + + const normalized = normalizeAddress(address) ?? address; + + return ( + + {label ?? shortenAddress(address, 8)} + + ); +} + +function DetailPill({ children }: { children: React.ReactNode }) { + return {children}; +} + +function PayloadDetails({ label, payload }: { label: string; payload?: string }) { + if (!payload) return null; + const decoded = decodeTextCommentPayload(payload); + + return ( +
+ {label}: + {decoded ? ( + Comment “{decoded}” + ) : ( + {payload.length > 48 ? `${payload.slice(0, 48)}...` : payload} + )} +
+ ); +} + +function renderDetails(details: Array) { + const filtered = details.filter((detail): detail is string => Boolean(detail)); + if (filtered.length === 0) return null; + + return ( +
+ {filtered.map((detail) => ( + {detail} + ))} +
+ ); +} + +function RawMessageAction({ message, index }: { message: TransactionRequestMessage; index: number }) { + return ( +
+
+
+
Message #{index + 1}
+
+ To +
+
+
+ {formatNanoTonAmount(message.amount)} +
+
+ {renderDetails([ + message.stateInit ? 'State init' : undefined, + message.mode !== undefined ? `Mode ${message.mode}` : undefined, + message.extraCurrency && Object.keys(message.extraCurrency).length > 0 ? 'Extra currencies' : undefined, + ])} + +
+ ); +} + +function TonItemAction({ item, index }: { item: Extract; index: number }) { + return ( +
+
+
+
Send TON #{index + 1}
+
+ To +
+
+
+ {formatNanoTonAmount(item.amount)} +
+
+ {renderDetails([ + item.stateInit ? 'State init' : undefined, + item.extraCurrency && Object.keys(item.extraCurrency).length > 0 ? 'Extra currencies' : undefined, + ])} + +
+ ); +} + +function JettonItemAction({ item, index }: { item: Extract; index: number }) { + const jettonInfo = useJettonInfo(item.master); + + return ( +
+
+
+
Send jetton #{index + 1}
+
+ To +
+
+ {jettonInfo?.image && } + {jettonInfo?.name ?? 'Jetton'} + +
+
+
+ {formatTokenAmount(item.amount, jettonInfo?.decimals ?? 9, jettonInfo?.symbol)} +
+
+ {renderDetails([ + item.attachAmount ? `Attach ${formatNanoTonAmount(item.attachAmount)}` : undefined, + item.forwardAmount ? `Forward ${formatNanoTonAmount(item.forwardAmount)}` : undefined, + item.responseDestination ? `Response ${shortenAddress(item.responseDestination, 8)}` : undefined, + ])} + + +
+ ); +} + +function NftItemAction({ item, index }: { item: Extract; index: number }) { + return ( +
+
+
+
Transfer NFT #{index + 1}
+
+ To +
+
+ NFT +
+
+
+ {renderDetails([ + item.attachAmount ? `Attach ${formatNanoTonAmount(item.attachAmount)}` : undefined, + item.forwardAmount ? `Forward ${formatNanoTonAmount(item.forwardAmount)}` : undefined, + item.responseDestination ? `Response ${shortenAddress(item.responseDestination, 8)}` : undefined, + ])} + + +
+ ); +} + +function StructuredItemAction({ item, index }: { item: StructuredItem; index: number }) { + if (item.type === 'ton') return ; + if (item.type === 'jetton') return ; + return ; +} + +export function TransactionRequestDetails({ request, title = 'You will sign:' }: TransactionRequestDetailsProps) { + const items = request.items ?? []; + const messages = request.messages ?? []; + const hasItems = items.length > 0; + const count = hasItems ? items.length : messages.length; + + return ( +
+
{title}
+
+ {count === 0 ? ( +
+

No outgoing messages in this request

+
+ ) : hasItems ? ( + items.map((item, index) => ( + + )) + ) : ( + messages.map((message, index) => ( + + )) + )} +
+
+ ); +} diff --git a/apps/demo-wallet/src/components/TransactionRequestModal.tsx b/apps/demo-wallet/src/components/TransactionRequestModal.tsx index 33b10cca8..e5144529c 100644 --- a/apps/demo-wallet/src/components/TransactionRequestModal.tsx +++ b/apps/demo-wallet/src/components/TransactionRequestModal.tsx @@ -6,36 +6,16 @@ * */ -import React, { memo, useEffect, useMemo, useState } from 'react'; -import type { - JettonInfo, - TransactionEmulatedPreview, - SendTransactionRequestEvent, - TransactionTraceMoneyFlowItem, -} from '@ton/walletkit'; +import React from 'react'; +import type { SendTransactionRequestEvent } from '@ton/walletkit'; import { getNormalizedExtMessageHash } from '@ton/walletkit'; -import { Address } from '@ton/core'; -import { - useWalletKit, - useAuth, - useWalletStore, - useTransactionRequests, - getChainNetwork, - getTransactionExplorerUrls, -} from '@demo/wallet-core'; -import type { SavedWallet, NetworkType } from '@demo/wallet-core'; +import { getTransactionExplorerUrls, useTransactionRequests } from '@demo/wallet-core'; +import type { SavedWallet } from '@demo/wallet-core'; import { toast } from 'sonner'; -import { Button } from './Button'; -import { Card } from './Card'; -import { DAppInfo } from './DAppInfo'; -import { WalletPreview } from './WalletPreview'; -import { HoldToSignButton } from './HoldToSignButton'; -import { createComponentLogger } from '../utils/logger'; -import { formatUnits } from '../utils/units'; -// Create logger for transaction request modal - -const log = createComponentLogger('TransactionRequestModal'); +import { RequestModal } from './RequestModal'; +import { TransactionRequestDetails } from './TransactionRequestDetails'; +import { useActiveWalletNetwork } from '../hooks/useJettonInfo'; interface TransactionRequestModalProps { request: SendTransactionRequestEvent; @@ -44,109 +24,31 @@ interface TransactionRequestModalProps { } export const TransactionRequestModal: React.FC = ({ request, savedWallets, isOpen }) => { - const walletKit = useWalletKit(); - const isAuthenticated = useWalletStore((state) => state.walletManagement.isAuthenticated); const network = useActiveWalletNetwork(); - const [isLoading, setIsLoading] = useState(false); - const [showSuccess, setShowSuccess] = useState(false); - const [isExpired, setIsExpired] = useState(false); - const [localPreview, setPreview] = useState(undefined); - const { holdToSign } = useAuth(); const { approveTransactionRequest, rejectTransactionRequest } = useTransactionRequests(); - // Find the wallet being used for this transaction - const currentWallet = useMemo(() => { - if (!request.walletAddress) return null; - return savedWallets.find((wallet) => wallet.kitWalletId === request.walletId) || null; - }, [savedWallets, request.walletAddress]); - - // Check every second if transaction has expired - useEffect(() => { - const checkExpiration = () => { - const validUntil = request.request?.validUntil; - if (validUntil) { - const now = Math.floor(Date.now() / 1000); - setIsExpired(validUntil < now); - } else { - setIsExpired(false); - } - }; - - // Check immediately - checkExpiration(); - - // Set up interval to check every second - const interval = setInterval(checkExpiration, 1000); - - return () => clearInterval(interval); - }, [request.request?.validUntil]); - - useEffect(() => { - if (!isAuthenticated) return; - async function updatePreview() { - if (request.preview.data) { - return; - } - await walletKit?.ensureInitialized(); - const preview = await walletKit?.getWallet(request.walletId ?? '')?.getTransactionPreview(request.request); - setPreview(preview); - } - updatePreview(); - }, [request.walletId, request.request, request.preview, walletKit, isAuthenticated]); - - const preview = useMemo(() => { - return localPreview ?? request.preview.data; - }, [request, localPreview]); - - // Reset success state when modal closes/opens - useEffect(() => { - if (!isOpen) { - setShowSuccess(false); - setIsLoading(false); - setIsExpired(false); - } - }, [isOpen]); - const handleApprove = async () => { - setIsLoading(true); - try { - const result = await approveTransactionRequest(); - - if (result?.signedBoc) { - const { hash } = getNormalizedExtMessageHash(result.signedBoc); - const { tonScan, tonViewer } = getTransactionExplorerUrls(hash, network); - toast.success('Transaction is sent to the network', { - description: ( - - - TonScan - - - TonViewer - - - ), - }); - } - - setIsLoading(false); - setShowSuccess(true); - } catch (error) { - log.error('Failed to approve transaction:', error); - toast.error('Failed to approve transaction', { - description: (error as Error)?.message, + const result = await approveTransactionRequest(); + if (result?.signedBoc) { + const { hash } = getNormalizedExtMessageHash(result.signedBoc); + const { tonScan, tonViewer } = getTransactionExplorerUrls(hash, network); + toast.success('Transaction is sent to the network', { + description: ( + + + TonScan + + + TonViewer + + + ), }); - setIsLoading(false); } }; @@ -154,366 +56,34 @@ export const TransactionRequestModal: React.FC = ( rejectTransactionRequest('User rejected the transaction'); }; - if (!isOpen) return null; - - // Success state view - if (showSuccess) { - return ( -
- -
- {/* Success Content */} -
- {/* Success Icon */} -
-
- - - -
-
- - {/* Success Message */} -
-

Success!

-

Transaction signed successfully

-
-
-
-
- ); - } - return ( -
-
- -
- {/* Header */} -
-

- Transaction Request -

-

- A dApp wants to send a transaction from your wallet -

-
- - {/* dApp Information */} - - - {/* Wallet Information */} - {currentWallet && ( -
- {/*

Signing with:

*/} - -
- )} - - {/* Expired Transaction Warning */} - {isExpired ? ( -
-
-
- - - -
-
-

Transaction Expired

-

- This transaction request has expired and can no longer be signed. Please - reject it and request a new transaction from the dApp. -

-
-
-
- ) : ( - <> - {preview && preview.result === 'success' && ( - <> - {/* Money Flow Summary */} -
-
- {/* No transfers message */} - {preview.moneyFlow?.outputs === '0' && - preview.moneyFlow?.inputs === '0' && - preview.moneyFlow?.ourTransfers.length === 0 ? ( -
-

- This transaction doesn't involve any token transfers -

-
- ) : ( - - )} -
-
- {/* Parsed Actions from Emulation */} - {/*{request.preview.data.result && (*/} - {/* */} - {/*)}*/} - - )} - - {preview && (preview.result === 'failure' || preview.error) && ( -
-

- Error: {preview.error?.message} -

-
- )} - - {/* Warning */} -
-
-
- - - -
-
-

- Warning: This transaction will be irreversible. Only - approve if you trust the requesting dApp and understand the transaction - details. -

-
-
-
- - )} - - {/* Action Buttons */} -
- - {!isExpired && - (holdToSign ? ( - - ) : ( - - ))} -
-
-
-
-
+ } + warning={{ + tone: 'red', + message: ( + <> + Warning: This transaction will be irreversible. Only approve if you trust the + requesting dApp and understand the transaction details. + + ), + }} + approveLabel="Approve & Sign" + successMessage="Transaction signed successfully" + testIds={{ + request: 'request', + approve: 'send-transaction-approve', + reject: 'send-transaction-reject', + }} + onApprove={handleApprove} + onReject={handleReject} + loggerName="TransactionRequestModal" + previewMode="send" + /> ); }; - -function useActiveWalletNetwork(): NetworkType { - const savedWallets = useWalletStore((state) => state.walletManagement.savedWallets); - const activeWalletId = useWalletStore((state) => state.walletManagement.activeWalletId); - const activeWallet = savedWallets.find((w) => w.id === activeWalletId); - return activeWallet?.network || 'testnet'; -} - -function useJettonInfo(jettonAddress: Address | string | null) { - const walletKit = useWalletKit(); - const network = useActiveWalletNetwork(); - const [jettonInfo, setJettonInfo] = useState(null); - const chainNetwork = getChainNetwork(network); - - useEffect(() => { - if (!jettonAddress) { - setJettonInfo(null); - return; - } - async function updateJettonInfo() { - if (!jettonAddress) { - return; - } - const jettonInfo = await walletKit?.jettons?.getJettonInfo(jettonAddress.toString(), chainNetwork); - setJettonInfo(jettonInfo ?? null); - } - updateJettonInfo(); - }, [jettonAddress, walletKit, chainNetwork]); - return jettonInfo; -} - -function SafeParseAddress(address: string) { - try { - return Address.parse(address).toString(); - } catch { - return null; - } -} - -export const JettonNameDisplay = memo(function JettonNameDisplay({ - jettonAddress, -}: { - jettonAddress: Address | string | undefined; -}) { - const jettonInfo = useJettonInfo( - jettonAddress - ? typeof jettonAddress === 'string' && jettonAddress !== 'TON' - ? SafeParseAddress(jettonAddress) - : jettonAddress - : null, - ); - - const name = jettonInfo?.name; - return
{name ?? jettonAddress?.toString() ?? 'UNKNOWN'}
; -}); - -export const JettonAmountDisplay = memo(function JettonAmountDisplay({ - amount, - jettonAddress, -}: { - amount: bigint; - jettonAddress: Address | string | undefined; -}) { - const jettonInfo = useJettonInfo( - jettonAddress - ? typeof jettonAddress === 'string' && jettonAddress !== 'TON' - ? SafeParseAddress(jettonAddress) - : jettonAddress - : null, - ); - - return ( -
- {formatUnits(amount, jettonInfo?.decimals ?? 9)} {jettonInfo?.symbol ?? 'UNKWN'} -
- ); -}); - -export const JettonImage = memo(function JettonImage({ - jettonAddress, -}: { - jettonAddress: Address | string | undefined; -}) { - const jettonInfo = useJettonInfo( - jettonAddress - ? typeof jettonAddress === 'string' && jettonAddress !== 'TON' - ? SafeParseAddress(jettonAddress) - : jettonAddress - : null, - ); - - return {jettonInfo?.name}; - // return <>; -}); - -const JettonFlowItem = memo(function JettonFlowItem({ - jettonAddress, - amount, -}: { - jettonAddress: Address | string | undefined; - amount: string; -}) { - return ( -
- - - - -
= 0n ? 'text-green-600' : 'text-red-600'}`}> - {BigInt(amount) >= 0n ? '+' : ''} - -
-
- ); -}); -export const JettonFlow = memo(function JettonFlow({ transfers }: { transfers: TransactionTraceMoneyFlowItem[] }) { - return ( -
-
Money Flow:
-
- {/* */} - {transfers?.length > 0 ? ( - transfers.map((transfer) => - transfer.assetType === 'jetton' ? ( - - ) : ( - - ), - ) - ) : ( - <> - )} -
-
- ); -}); - -// no-op diff --git a/apps/demo-wallet/src/components/index.ts b/apps/demo-wallet/src/components/index.ts index 05e4d3792..757c26d4f 100644 --- a/apps/demo-wallet/src/components/index.ts +++ b/apps/demo-wallet/src/components/index.ts @@ -30,6 +30,7 @@ export { StreamingStatus } from './StreamingStatus'; export { ProtectedRoute } from './ProtectedRoute'; export { RecentTransactions } from './RecentTransactions'; export { SignDataRequestModal } from './SignDataRequestModal'; +export { SignMessageRequestModal } from './SignMessageRequestModal'; export { TraceRow } from './TraceRow'; export { TransactionRequestModal } from './TransactionRequestModal'; export { WalletPreview } from './WalletPreview'; diff --git a/apps/demo-wallet/src/components/transactions/TransactionCard.tsx b/apps/demo-wallet/src/components/transactions/TransactionCard.tsx index 0f139ee3b..6b877dd08 100644 --- a/apps/demo-wallet/src/components/transactions/TransactionCard.tsx +++ b/apps/demo-wallet/src/components/transactions/TransactionCard.tsx @@ -210,12 +210,25 @@ export const TransactionCard: React.FC = memo( ); } + const isExternal = /^https?:\/\//.test(traceLink); + const className = 'block py-2 hover:bg-gray-50/50 -mx-1 px-1 rounded transition-colors'; + + if (isExternal) { + return ( + + {inner} + + ); + } + return ( - + {inner} ); diff --git a/apps/demo-wallet/src/hooks/index.ts b/apps/demo-wallet/src/hooks/index.ts index 1b18dd706..eacc34b80 100644 --- a/apps/demo-wallet/src/hooks/index.ts +++ b/apps/demo-wallet/src/hooks/index.ts @@ -6,4 +6,8 @@ * */ +export * from './useFormattedJetton'; +export * from './useJettonInfo'; +export * from './usePasteHandler'; export * from './useTonWallet'; +export * from './useWalletDataUpdater'; diff --git a/apps/demo-wallet/src/hooks/useJettonInfo.ts b/apps/demo-wallet/src/hooks/useJettonInfo.ts new file mode 100644 index 000000000..37ceedd2b --- /dev/null +++ b/apps/demo-wallet/src/hooks/useJettonInfo.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { useEffect, useState } from 'react'; +import type { Address } from '@ton/core'; +import type { JettonInfo } from '@ton/walletkit'; +import { getChainNetwork, useWalletKit, useWalletStore } from '@demo/wallet-core'; +import type { NetworkType } from '@demo/wallet-core'; + +import { normalizeAddress } from '../utils/formatters'; + +export type TokenInfo = Partial> & { + decimals?: number; +}; + +export const TON_INFO: TokenInfo = { + name: 'TON', + symbol: 'TON', + decimals: 9, + image: '/ton.svg', +}; + +export function useActiveWalletNetwork(): NetworkType { + const savedWallets = useWalletStore((state) => state.walletManagement.savedWallets); + const activeWalletId = useWalletStore((state) => state.walletManagement.activeWalletId); + const activeWallet = savedWallets.find((w) => w.id === activeWalletId); + return activeWallet?.network || 'testnet'; +} + +export function resolveTokenAddress(tokenAddress: Address | string | undefined) { + if (!tokenAddress) return null; + if (typeof tokenAddress === 'string') { + return normalizeAddress(tokenAddress) ?? tokenAddress; + } + return tokenAddress; +} + +export function useJettonInfo(tokenAddress: Address | string | null | undefined) { + const walletKit = useWalletKit(); + const network = useActiveWalletNetwork(); + const [tokenInfo, setTokenInfo] = useState(null); + const chainNetwork = getChainNetwork(network); + + useEffect(() => { + if (!tokenAddress) { + setTokenInfo(null); + return; + } + + async function updateTokenInfo() { + if (!tokenAddress) return; + const info = await walletKit?.jettons?.getJettonInfo(tokenAddress.toString(), chainNetwork); + setTokenInfo(info ?? null); + } + + updateTokenInfo(); + }, [tokenAddress, walletKit, chainNetwork]); + + return tokenInfo; +} diff --git a/apps/demo-wallet/src/pages/TonConnectRoute.tsx b/apps/demo-wallet/src/pages/TonConnectRoute.tsx new file mode 100644 index 000000000..b77f15dc5 --- /dev/null +++ b/apps/demo-wallet/src/pages/TonConnectRoute.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React, { useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTonConnect } from '@demo/wallet-core'; + +import { Layout } from '../components'; +import { LoaderCircle } from '../components/LoaderCircle'; +import { createComponentLogger } from '../utils/logger'; + +const log = createComponentLogger('TonConnectRoute'); + +export const TonConnectRoute: React.FC = () => { + const navigate = useNavigate(); + const { handleTonConnectUrl } = useTonConnect(); + const hasHandled = useRef(false); + + useEffect(() => { + if (hasHandled.current) return; + hasHandled.current = true; + + const url = window.location.href; + handleTonConnectUrl(url) + .catch((err) => log.error('Failed to handle TON Connect URL:', err)) + .finally(() => navigate('/wallet', { replace: true })); + }, [handleTonConnectUrl, navigate]); + + return ( + +
+ +
+
+ ); +}; diff --git a/apps/demo-wallet/src/pages/WalletDashboard.tsx b/apps/demo-wallet/src/pages/WalletDashboard.tsx index 61feaece0..aa70f5f1a 100644 --- a/apps/demo-wallet/src/pages/WalletDashboard.tsx +++ b/apps/demo-wallet/src/pages/WalletDashboard.tsx @@ -8,7 +8,14 @@ import React, { useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useWallet, useWalletKit, useTonConnect, useTransactionRequests, useSignDataRequests } from '@demo/wallet-core'; +import { + useWallet, + useWalletKit, + useTonConnect, + useTransactionRequests, + useSignDataRequests, + useSignMessageRequests, +} from '@demo/wallet-core'; import { AnimatedBalance, @@ -18,6 +25,7 @@ import { ConnectRequestModal, TransactionRequestModal, SignDataRequestModal, + SignMessageRequestModal, DisconnectNotifications, NftsCard, RecentTransactions, @@ -64,6 +72,7 @@ export const WalletDashboard: React.FC = () => { const { pendingTransactionRequest, isTransactionModalOpen } = useTransactionRequests(); const { pendingSignDataRequest, isSignDataModalOpen, approveSignDataRequest, rejectSignDataRequest } = useSignDataRequests(); + const { pendingSignMessageRequest, isSignMessageModalOpen } = useSignMessageRequests(); const { error } = useTonWallet(); // Use the paste handler hook @@ -380,6 +389,15 @@ export const WalletDashboard: React.FC = () => { onReject={rejectSignDataRequest} /> )} + + {/* Sign Message Request Modal */} + {pendingSignMessageRequest && ( + + )} ); }; diff --git a/apps/demo-wallet/src/pages/index.ts b/apps/demo-wallet/src/pages/index.ts index fb0c7a821..125c5f0e6 100644 --- a/apps/demo-wallet/src/pages/index.ts +++ b/apps/demo-wallet/src/pages/index.ts @@ -15,3 +15,4 @@ export * from './TracePage'; export * from './TransactionDetail'; export * from './Swap'; export * from './Staking'; +export * from './TonConnectRoute'; diff --git a/apps/demo-wallet/src/utils/formatters.ts b/apps/demo-wallet/src/utils/formatters.ts index 82f1fead7..84585d826 100644 --- a/apps/demo-wallet/src/utils/formatters.ts +++ b/apps/demo-wallet/src/utils/formatters.ts @@ -7,6 +7,21 @@ */ import { Address } from '@ton/core'; +import { Base64ToHex } from '@ton/walletkit'; + +export function normalizeAddress(address: string): string | null { + try { + return Address.parse(address).toString(); + } catch { + return null; + } +} + +export function shortenAddress(addr?: string, count = 6): string { + if (!addr) return ''; + const normalized = normalizeAddress(addr) ?? addr; + return normalized.length <= count * 2 ? normalized : `${normalized.slice(0, count)}...${normalized.slice(-count)}`; +} /** * Compare two TON addresses for equality (handles different formats: 0:xxx, EQxxx, UQxxx) @@ -52,6 +67,29 @@ export const formatTonForDisplay = (amountOrValue: string): string => { * @returns Shortened address (e.g., "EQAbc...xyz123") */ export const formatAddress = (addr: string): string => { - if (!addr) return ''; - return `${addr.slice(0, 6)}...${addr.slice(-6)}`; + return shortenAddress(addr); }; + +type ExplorerNetwork = 'mainnet' | 'testnet' | 'tetra'; + +function getTonviewerHost(network: ExplorerNetwork): string { + if (network === 'testnet') return 'testnet.tonviewer.com'; + if (network === 'tetra') return 'tetra.tonviewer.com'; + return 'tonviewer.com'; +} + +function toHexHash(hash: string): string { + if (/^(0x)?[0-9a-fA-F]+$/.test(hash)) { + return hash.startsWith('0x') ? hash.slice(2) : hash; + } + try { + const hex = Base64ToHex(hash); + return hex.startsWith('0x') ? hex.slice(2) : hex; + } catch { + return hash; + } +} + +export function getTonviewerTxUrl(network: ExplorerNetwork, hash: string): string { + return `https://${getTonviewerHost(network)}/transaction/${toHexHash(hash)}`; +} diff --git a/apps/demo-wallet/src/utils/index.ts b/apps/demo-wallet/src/utils/index.ts index 36f9bd290..664a2a940 100644 --- a/apps/demo-wallet/src/utils/index.ts +++ b/apps/demo-wallet/src/utils/index.ts @@ -7,4 +7,5 @@ */ export * from './formatters'; +export * from './payload'; export * from './units'; diff --git a/apps/demo-wallet/src/utils/payload.ts b/apps/demo-wallet/src/utils/payload.ts new file mode 100644 index 000000000..2c07c35dd --- /dev/null +++ b/apps/demo-wallet/src/utils/payload.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { Cell } from '@ton/core'; + +export function decodeTextCommentPayload(payload: string): string | null { + try { + const slice = Cell.fromBase64(payload).beginParse(); + if (slice.remainingBits < 32) return null; + const op = slice.loadUint(32); + if (op !== 0) return null; + const text = slice.loadStringTail(); + return text.length > 0 ? text : null; + } catch { + return null; + } +} diff --git a/apps/demo-wallet/src/utils/units.ts b/apps/demo-wallet/src/utils/units.ts index dbefab7bf..4a589e88a 100644 --- a/apps/demo-wallet/src/utils/units.ts +++ b/apps/demo-wallet/src/utils/units.ts @@ -86,3 +86,19 @@ export function parseTon(value: string) { export function formatTon(value: bigint | string) { return formatUnits(value, 9); } + +export function formatNanoTonAmount(value?: bigint | string) { + try { + return `${formatTon(value ?? '0')} TON`; + } catch { + return `${value ?? '0'} nanotons`; + } +} + +export function formatTokenAmount(value: bigint | string, decimals = 9, symbol?: string) { + try { + return `${formatUnits(value, decimals)}${symbol ? ` ${symbol}` : ''}`; + } catch { + return `${value}${symbol ? ` ${symbol}` : ''}`; + } +} diff --git a/apps/demo-wallet/src/utils/walletManifest.ts b/apps/demo-wallet/src/utils/walletManifest.ts index de8175dd9..30bf1948d 100644 --- a/apps/demo-wallet/src/utils/walletManifest.ts +++ b/apps/demo-wallet/src/utils/walletManifest.ts @@ -41,14 +41,24 @@ export function getTonConnectDeviceInfo(): DeviceInfo { export function getTonConnectFeatures(): Feature[] { return [ - 'SendTransaction', { name: 'SendTransaction', maxMessages: 4, + extraCurrencySupported: true, + itemTypes: ['ton', 'jetton', 'nft'], }, { name: 'SignData', types: ['text', 'binary', 'cell'], }, + { + name: 'EmbeddedRequest', + }, + { + name: 'SignMessage', + maxMessages: 255, + extraCurrencySupported: true, + itemTypes: ['ton', 'jetton', 'nft'], + }, ]; } diff --git a/demo/wallet-core/src/hooks/useWalletStore.ts b/demo/wallet-core/src/hooks/useWalletStore.ts index 3f431415a..a373b8738 100644 --- a/demo/wallet-core/src/hooks/useWalletStore.ts +++ b/demo/wallet-core/src/hooks/useWalletStore.ts @@ -141,6 +141,22 @@ export const useSignDataRequests = () => { ); }; +/** + * Hook for sign message requests + */ +export const useSignMessageRequests = () => { + return useWalletStore( + useShallow((state) => ({ + pendingSignMessageRequest: state.tonConnect.pendingSignMessageRequestEvent, + isSignMessageModalOpen: state.tonConnect.isSignMessageModalOpen, + showSignMessageRequest: state.showSignMessageRequest, + approveSignMessageRequest: state.approveSignMessageRequest, + rejectSignMessageRequest: state.rejectSignMessageRequest, + closeSignMessageModal: state.closeSignMessageModal, + })), + ); +}; + /** * Hook for disconnect events */ diff --git a/demo/wallet-core/src/index.ts b/demo/wallet-core/src/index.ts index 2e1f8fdd9..0d7096352 100644 --- a/demo/wallet-core/src/index.ts +++ b/demo/wallet-core/src/index.ts @@ -26,6 +26,7 @@ export { useTonConnect, useTransactionRequests, useSignDataRequests, + useSignMessageRequests, useDisconnectEvents, useNfts, useJettons, diff --git a/demo/wallet-core/src/store/slices/tonConnectSlice.ts b/demo/wallet-core/src/store/slices/tonConnectSlice.ts index 455d128f9..1cd7a1f75 100644 --- a/demo/wallet-core/src/store/slices/tonConnectSlice.ts +++ b/demo/wallet-core/src/store/slices/tonConnectSlice.ts @@ -12,6 +12,7 @@ import type { SendTransactionRequestEvent, ConnectionRequestEvent, SignDataRequestEvent, + SignMessageRequestEvent, DisconnectionEvent, } from '@ton/walletkit'; @@ -39,6 +40,8 @@ export const createTonConnectSlice: TonConnectSliceCreator = (set: SetState, get isTransactionModalOpen: false, pendingSignDataRequestEvent: undefined, isSignDataModalOpen: false, + pendingSignMessageRequestEvent: undefined, + isSignMessageModalOpen: false, disconnectedSessions: [], }, @@ -85,17 +88,40 @@ export const createTonConnectSlice: TonConnectSliceCreator = (set: SetState, get walletId: selectedWallet.getWalletId(), }; - await state.walletCore.walletKit.approveConnectRequest(event); + const embeddedRequest = await state.walletCore.walletKit.approveConnectRequest(event); + state.clearCurrentRequestFromQueue(); set((state) => { state.tonConnect.pendingConnectRequestEvent = undefined; state.tonConnect.isConnectModalOpen = false; }); + + if (embeddedRequest) { + switch (embeddedRequest.type) { + case 'sendTransaction': + get().enqueueRequest({ + type: 'transaction', + request: embeddedRequest, + }); + break; + case 'signMessage': + get().enqueueRequest({ + type: 'signMessage', + request: embeddedRequest, + }); + break; + case 'signData': + get().enqueueRequest({ + type: 'signData', + request: embeddedRequest, + }); + break; + } + } } catch (error) { log.error('Failed to approve connect request:', error); - throw error; - } finally { state.clearCurrentRequestFromQueue(); + throw error; } }, @@ -298,6 +324,77 @@ export const createTonConnectSlice: TonConnectSliceCreator = (set: SetState, get get().clearCurrentRequestFromQueue(); }, + // Sign message request actions + showSignMessageRequest: (request: SignMessageRequestEvent) => { + set((state) => { + state.tonConnect.pendingSignMessageRequestEvent = request; + state.tonConnect.isSignMessageModalOpen = true; + }); + }, + + approveSignMessageRequest: async () => { + const state = get(); + if (!state.tonConnect.pendingSignMessageRequestEvent) { + log.error('No pending sign message request to approve'); + return; + } + if (!state.walletCore.walletKit) { + throw new Error('WalletKit not initialized'); + } + try { + await state.walletCore.walletKit.approveSignMessageRequest(state.tonConnect.pendingSignMessageRequestEvent); + setTimeout(() => { + set((state) => { + state.tonConnect.pendingSignMessageRequestEvent = undefined; + state.tonConnect.isSignMessageModalOpen = false; + }); + state.clearCurrentRequestFromQueue(); + }, 3000); + } catch (error) { + log.error('Failed to approve sign message request:', error); + state.clearCurrentRequestFromQueue(); + throw error; + } + }, + + rejectSignMessageRequest: async (reason?: string) => { + const state = get(); + if (!state.tonConnect.pendingSignMessageRequestEvent) { + log.error('No pending sign message request to reject'); + return; + } + if (!state.walletCore.walletKit) { + set((state) => { + state.tonConnect.pendingSignMessageRequestEvent = undefined; + state.tonConnect.isSignMessageModalOpen = false; + }); + state.clearCurrentRequestFromQueue(); + return; + } + try { + await state.walletCore.walletKit.rejectSignMessageRequest( + state.tonConnect.pendingSignMessageRequestEvent, + reason, + ); + } catch (error) { + log.error('Failed to reject sign message request:', error); + } finally { + set((state) => { + state.tonConnect.pendingSignMessageRequestEvent = undefined; + state.tonConnect.isSignMessageModalOpen = false; + }); + state.clearCurrentRequestFromQueue(); + } + }, + + closeSignMessageModal: () => { + set((state) => { + state.tonConnect.isSignMessageModalOpen = false; + state.tonConnect.pendingSignMessageRequestEvent = undefined; + }); + get().clearCurrentRequestFromQueue(); + }, + // Disconnect events handleDisconnectEvent: (event: DisconnectionEvent) => { log.info('Disconnect event received:', event); @@ -395,6 +492,8 @@ export const createTonConnectSlice: TonConnectSliceCreator = (set: SetState, get get().showTransactionRequest(nextRequest.request); } else if (nextRequest.type === 'signData') { get().showSignDataRequest(nextRequest.request); + } else if (nextRequest.type === 'signMessage') { + get().showSignMessageRequest(nextRequest.request); } }, @@ -494,6 +593,14 @@ export const createTonConnectSlice: TonConnectSliceCreator = (set: SetState, get }); }); + walletKit.onSignMessageRequest((event) => { + log.info('Sign message request received:', event); + get().enqueueRequest({ + type: 'signMessage', + request: event, + }); + }); + walletKit.onDisconnect((event) => { log.info('Disconnect event received:', event); get().handleDisconnectEvent(event); diff --git a/demo/wallet-core/src/types/store.ts b/demo/wallet-core/src/types/store.ts index c44f84708..c01117542 100644 --- a/demo/wallet-core/src/types/store.ts +++ b/demo/wallet-core/src/types/store.ts @@ -18,6 +18,7 @@ import type { ConnectionRequestEvent, SendTransactionRequestEvent, SignDataRequestEvent, + SignMessageRequestEvent, DisconnectionEvent, WalletAdapter, SwapQuote, @@ -151,6 +152,8 @@ export interface TonConnectSlice { isTransactionModalOpen: boolean; pendingSignDataRequestEvent?: SignDataRequestEvent; isSignDataModalOpen: boolean; + pendingSignMessageRequestEvent?: SignMessageRequestEvent; + isSignMessageModalOpen: boolean; disconnectedSessions: DisconnectNotification[]; }; @@ -173,6 +176,12 @@ export interface TonConnectSlice { rejectSignDataRequest: (reason?: string) => Promise; closeSignDataModal: () => void; + // Sign message request actions + showSignMessageRequest: (request: SignMessageRequestEvent) => void; + approveSignMessageRequest: () => Promise; + rejectSignMessageRequest: (reason?: string) => Promise; + closeSignMessageModal: () => void; + // Disconnect event actions handleDisconnectEvent: (event: DisconnectionEvent) => void; clearDisconnectNotifications: () => void; diff --git a/demo/wallet-core/src/types/wallet.ts b/demo/wallet-core/src/types/wallet.ts index ced11714d..8f4e403aa 100644 --- a/demo/wallet-core/src/types/wallet.ts +++ b/demo/wallet-core/src/types/wallet.ts @@ -11,6 +11,7 @@ import type { JSBridgeTransportFunction, StorageAdapter as KitStorageAdapter, SignDataRequestEvent, + SignMessageRequestEvent, SendTransactionRequestEvent, AnalyticsManagerOptions, } from '@ton/walletkit'; @@ -94,7 +95,16 @@ export interface QueuedRequestSignData { request: SignDataRequestEvent; } -export type QueuedRequestData = QueuedRequestConnect | QueuedRequestTransaction | QueuedRequestSignData; +export interface QueuedRequestSignMessage { + type: 'signMessage'; + request: SignMessageRequestEvent; +} + +export type QueuedRequestData = + | QueuedRequestConnect + | QueuedRequestTransaction + | QueuedRequestSignData + | QueuedRequestSignMessage; export type QueuedRequest = QueueRequestBase & QueuedRequestData; diff --git a/demo/wallet-core/src/utils/explorer-urls.ts b/demo/wallet-core/src/utils/explorer-urls.ts index d0f47f92b..52896d140 100644 --- a/demo/wallet-core/src/utils/explorer-urls.ts +++ b/demo/wallet-core/src/utils/explorer-urls.ts @@ -22,3 +22,11 @@ export function getTransactionExplorerUrls(hash: string, network: NetworkType): tonViewer: `https://${prefix}tonviewer.com/transaction/${hashClean}`, }; } + +export function getAddressExplorerUrls(address: string, network: NetworkType): { tonScan: string; tonViewer: string } { + const prefix = getPrefix(network); + return { + tonScan: `https://${prefix}tonscan.org/address/${address}`, + tonViewer: `https://${prefix}tonviewer.com/${address}`, + }; +} diff --git a/demo/wallet-core/src/utils/walletManifest.ts b/demo/wallet-core/src/utils/walletManifest.ts index 92580b82f..c234b92a0 100644 --- a/demo/wallet-core/src/utils/walletManifest.ts +++ b/demo/wallet-core/src/utils/walletManifest.ts @@ -41,14 +41,24 @@ export function getTonConnectDeviceInfo(): DeviceInfo { export function getTonConnectFeatures(): Feature[] { return [ - 'SendTransaction', { name: 'SendTransaction', maxMessages: 4, + itemTypes: ['ton', 'jetton', 'nft'], + extraCurrencySupported: true, }, { name: 'SignData', types: ['text', 'binary', 'cell'], }, + { + name: 'SignMessage', + maxMessages: 4, + itemTypes: ['ton', 'jetton', 'nft'], + extraCurrencySupported: true, + }, + { + name: 'EmbeddedRequest', + }, ]; } diff --git a/package.json b/package.json index 46dddaaef..f2d86c630 100644 --- a/package.json +++ b/package.json @@ -43,5 +43,13 @@ "rimraf": "^6.1.2", "turbo": "2.9.6" }, - "packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b" + "packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b", + "pnpm": { + "overrides": { + "@tonconnect/protocol": "catalog:", + "@tonconnect/sdk": "catalog:", + "@tonconnect/ui": "catalog:", + "@tonconnect/ui-react": "catalog:" + } + } } diff --git a/packages/walletkit-android-bridge/src/api/eventListeners.ts b/packages/walletkit-android-bridge/src/api/eventListeners.ts index 08414c752..7bb5fe997 100644 --- a/packages/walletkit-android-bridge/src/api/eventListeners.ts +++ b/packages/walletkit-android-bridge/src/api/eventListeners.ts @@ -12,11 +12,13 @@ import type { RequestErrorEvent, SendTransactionRequestEvent, SignDataRequestEvent, + SignMessageRequestEvent, } from '@ton/walletkit'; type ConnectEventListener = ((event: ConnectionRequestEvent) => void) | null; type TransactionEventListener = ((event: SendTransactionRequestEvent) => void) | null; type SignDataEventListener = ((event: SignDataRequestEvent) => void) | null; +type SignMessageEventListener = ((event: SignMessageRequestEvent) => void) | null; type DisconnectEventListener = ((event: DisconnectionEvent) => void) | null; type ErrorEventListener = ((event: RequestErrorEvent) => void) | null; @@ -24,6 +26,7 @@ export const eventListeners = { onConnectListener: null as ConnectEventListener, onTransactionListener: null as TransactionEventListener, onSignDataListener: null as SignDataEventListener, + onSignMessageListener: null as SignMessageEventListener, onDisconnectListener: null as DisconnectEventListener, onErrorListener: null as ErrorEventListener, }; diff --git a/packages/walletkit-android-bridge/src/api/index.ts b/packages/walletkit-android-bridge/src/api/index.ts index 0e8fa13da..005e081bf 100644 --- a/packages/walletkit-android-bridge/src/api/index.ts +++ b/packages/walletkit-android-bridge/src/api/index.ts @@ -59,6 +59,8 @@ export const api = { rejectTransactionRequest: requests.rejectTransactionRequest, approveSignDataRequest: requests.approveSignDataRequest, rejectSignDataRequest: requests.rejectSignDataRequest, + approveSignMessageRequest: requests.approveSignMessageRequest, + rejectSignMessageRequest: requests.rejectSignMessageRequest, handleTonConnectUrl: tonconnect.handleTonConnectUrl, connectionEventFromUrl: tonconnect.connectionEventFromUrl, diff --git a/packages/walletkit-android-bridge/src/api/initialization.ts b/packages/walletkit-android-bridge/src/api/initialization.ts index 0266f3772..01ce0e65c 100644 --- a/packages/walletkit-android-bridge/src/api/initialization.ts +++ b/packages/walletkit-android-bridge/src/api/initialization.ts @@ -18,6 +18,7 @@ import type { RequestErrorEvent, SendTransactionRequestEvent, SignDataRequestEvent, + SignMessageRequestEvent, } from '@ton/walletkit'; import type { WalletKitBridgeInitConfig, SetEventsListenersArgs, WalletKitBridgeEventCallback } from '../types'; @@ -84,6 +85,16 @@ export async function setEventsListeners(args?: SetEventsListenersArgs): Promise kit.onSignDataRequest(eventListeners.onSignDataListener); + if (eventListeners.onSignMessageListener) { + kit.removeSignMessageRequestCallback(); + } + + eventListeners.onSignMessageListener = (event: SignMessageRequestEvent) => { + callback('signMessageRequest', event); + }; + + kit.onSignMessageRequest(eventListeners.onSignMessageListener); + if (eventListeners.onDisconnectListener) { kit.removeDisconnectCallback(); } @@ -129,6 +140,11 @@ export async function removeEventListeners(): Promise<{ ok: true }> { eventListeners.onSignDataListener = null; } + if (eventListeners.onSignMessageListener) { + kit.removeSignMessageRequestCallback(); + eventListeners.onSignMessageListener = null; + } + if (eventListeners.onDisconnectListener) { kit.removeDisconnectCallback(); eventListeners.onDisconnectListener = null; diff --git a/packages/walletkit-android-bridge/src/api/requests.ts b/packages/walletkit-android-bridge/src/api/requests.ts index c5512ae2c..ef84bbb85 100644 --- a/packages/walletkit-android-bridge/src/api/requests.ts +++ b/packages/walletkit-android-bridge/src/api/requests.ts @@ -31,3 +31,11 @@ export async function approveSignDataRequest(args: unknown[]) { export async function rejectSignDataRequest(args: unknown[]) { return kit('rejectSignDataRequest', ...args); } + +export async function approveSignMessageRequest(args: unknown[]) { + return kit('approveSignMessageRequest', ...args); +} + +export async function rejectSignMessageRequest(args: unknown[]) { + return kit('rejectSignMessageRequest', ...args); +} diff --git a/packages/walletkit-android-bridge/src/types/api.ts b/packages/walletkit-android-bridge/src/types/api.ts index 87874b061..d98b3631f 100644 --- a/packages/walletkit-android-bridge/src/types/api.ts +++ b/packages/walletkit-android-bridge/src/types/api.ts @@ -187,6 +187,18 @@ export interface RejectSignDataRequestArgs { reason?: string; } +export interface ApproveSignMessageRequestArgs { + event: TonConnectRequestEvent; + response?: { + internalBoc: string; + }; +} + +export interface RejectSignMessageRequestArgs { + event: TonConnectRequestEvent; + reason?: string; +} + export interface DisconnectSessionArgs { sessionId?: string; } @@ -453,6 +465,8 @@ export interface WalletKitBridgeApi { rejectTransactionRequest(args: RejectTransactionRequestArgs): PromiseOrValue<{ success: boolean }>; approveSignDataRequest(args: ApproveSignDataRequestArgs): PromiseOrValue<{ signature: string; timestamp: number }>; rejectSignDataRequest(args: RejectSignDataRequestArgs): PromiseOrValue<{ success: boolean }>; + approveSignMessageRequest(args: ApproveSignMessageRequestArgs): PromiseOrValue<{ internalBoc: string }>; + rejectSignMessageRequest(args: RejectSignMessageRequestArgs): PromiseOrValue<{ success: boolean }>; listSessions(): PromiseOrValue<{ items: TONConnectSession[] }>; disconnectSession(args?: DisconnectSessionArgs): PromiseOrValue<{ ok: boolean }>; getNfts(args: GetNftsArgs): PromiseOrValue; diff --git a/packages/walletkit-android-bridge/src/types/events.ts b/packages/walletkit-android-bridge/src/types/events.ts index 3fd100b3f..1c71a33c6 100644 --- a/packages/walletkit-android-bridge/src/types/events.ts +++ b/packages/walletkit-android-bridge/src/types/events.ts @@ -11,6 +11,7 @@ export type WalletKitBridgeEventType = | 'connectRequest' | 'transactionRequest' | 'signDataRequest' + | 'signMessageRequest' | 'disconnect' | 'requestError' | 'browserPageStarted' diff --git a/packages/walletkit-ios-bridge/src/main.ts b/packages/walletkit-ios-bridge/src/main.ts index 2dd2b4cc9..9a3b45444 100644 --- a/packages/walletkit-ios-bridge/src/main.ts +++ b/packages/walletkit-ios-bridge/src/main.ts @@ -27,6 +27,8 @@ import type { ConnectionApprovalResponse, SendTransactionRequestEvent, SignDataRequestEvent, + SignMessageRequestEvent, + SignMessageApprovalResponse, ApiClientConfig, ApiClient, SignatureDomain, @@ -38,6 +40,7 @@ import type { StreamingAPI, StakingProviderInterface, StakingAPI, + EmbeddedRequestEvent, } from '@ton/walletkit'; import { MemoryStorageAdapter, @@ -415,7 +418,7 @@ window.initWalletKit = async (configuration, storage, bridgeTransport, sessionMa async approveConnectRequest( event: ConnectionRequestEvent, response?: ConnectionApprovalResponse, - ): Promise { + ): Promise { if (!initialized) throw new Error('WalletKit Bridge not initialized'); console.log('✅ Bridge: Approving connect request:', event, event.walletAddress); @@ -507,6 +510,38 @@ window.initWalletKit = async (configuration, storage, bridgeTransport, sessionMa } }, + // Sign message handling + async approveSignMessageRequest( + event: SignMessageRequestEvent, + response?: SignMessageApprovalResponse, + ): Promise { + if (!initialized) throw new Error('WalletKit Bridge not initialized'); + console.log('✅ Bridge: Approving sign message request:', event); + + try { + const result = await walletKit.approveSignMessageRequest(event, response); + console.log('✅ Sign message request approved:', result); + return result; + } catch (error) { + console.error('❌ Failed to approve sign message request:', error); + throw error; + } + }, + + async rejectSignMessageRequest(event: SignMessageRequestEvent, reason?: string): Promise { + if (!initialized) throw new Error('WalletKit Bridge not initialized'); + console.log('❌ Bridge: Rejecting sign message request:', event, reason); + + try { + const result = await walletKit.rejectSignMessageRequest(event, reason); + console.log('✅ Sign message request rejected:', result); + return result; + } catch (error) { + console.error('❌ Failed to reject sign message request:', error); + throw error; + } + }, + // Session management async disconnect(sessionId: string): Promise { if (!initialized) throw new Error('WalletKit Bridge not initialized'); diff --git a/packages/walletkit-ios-bridge/src/types.ts b/packages/walletkit-ios-bridge/src/types.ts index 63a5e8a6f..4493e93ad 100644 --- a/packages/walletkit-ios-bridge/src/types.ts +++ b/packages/walletkit-ios-bridge/src/types.ts @@ -24,6 +24,8 @@ import type { ConnectionApprovalResponse, SendTransactionRequestEvent, SignDataRequestEvent, + SignMessageRequestEvent, + SignMessageApprovalResponse, SwapProviderInterface, SwapAPI, TonCenterStreamingProviderConfig, @@ -32,13 +34,13 @@ import type { StreamingAPI, StakingProviderInterface, StakingAPI, + ConnectionRequestEvent, + EmbeddedRequestEvent, } from '@ton/walletkit'; import type { OmnistonSwapProviderConfig } from '@ton/walletkit/swap/omniston'; import type { DeDustSwapProviderConfig } from '@ton/walletkit/swap/dedust'; import type { TonStakersProviderConfig } from '@ton/walletkit/staking/tonstakers'; -import type { ConnectionRequestEvent } from '../../walletkit/dist/cjs'; - export interface SwiftApiClient extends ApiClient { getNetwork: () => Network; } @@ -116,7 +118,10 @@ export interface SwiftWalletKit { handleTonConnectUrl(url: string): Promise; - approveConnectRequest(event: ConnectionRequestEvent, response?: ConnectionApprovalResponse): Promise; + approveConnectRequest( + event: ConnectionRequestEvent, + response?: ConnectionApprovalResponse, + ): Promise; rejectConnectRequest(event: ConnectionRequestEvent, reason?: string): Promise; @@ -133,6 +138,13 @@ export interface SwiftWalletKit { rejectSignDataRequest(event: SignDataRequestEvent, reason?: string): Promise; + approveSignMessageRequest( + event: SignMessageRequestEvent, + response?: SignMessageApprovalResponse, + ): Promise; + + rejectSignMessageRequest(event: SignMessageRequestEvent, reason?: string): Promise; + disconnect(sessionId: string): Promise; sendTransaction(wallet: Wallet, transaction: TransactionRequest): Promise; diff --git a/packages/walletkit/src/api/interfaces/Wallet.ts b/packages/walletkit/src/api/interfaces/Wallet.ts index 370323995..21dc49d4c 100644 --- a/packages/walletkit/src/api/interfaces/Wallet.ts +++ b/packages/walletkit/src/api/interfaces/Wallet.ts @@ -24,6 +24,7 @@ import type { NFTRawTransferRequest, } from '../models'; import type { WalletAdapter } from './WalletAdapter'; +import type { TransactionPreviewOptions } from '../../utils/toncenterEmulation'; export type Wallet = WalletAdapter & WalletTonInterface & @@ -34,7 +35,10 @@ export interface WalletTonInterface { createTransferTonTransaction(params: TONTransferRequest): Promise; createTransferMultiTonTransaction(params: [TONTransferRequest]): Promise; - getTransactionPreview(data: TransactionRequest | Promise): Promise; + getTransactionPreview( + data: TransactionRequest | Promise, + options?: TransactionPreviewOptions, + ): Promise; sendTransaction(request: TransactionRequest): Promise; diff --git a/packages/walletkit/src/api/interfaces/WalletAdapter.ts b/packages/walletkit/src/api/interfaces/WalletAdapter.ts index 9a8687a03..7ae165336 100644 --- a/packages/walletkit/src/api/interfaces/WalletAdapter.ts +++ b/packages/walletkit/src/api/interfaces/WalletAdapter.ts @@ -10,6 +10,7 @@ import type { Hex, Base64String, UserFriendlyAddress } from '../models/core/Prim import type { Network } from '../models/core/Network'; import type { ApiClient, WalletId } from '../..'; import type { TransactionRequest } from '../models/transactions/TransactionRequest'; +import type { SignedSendTransactionOptions } from '../models/transactions/SignedSendTransactionOptions'; import type { PreparedSignData } from '../models/core/PreparedSignData'; import type { ProofMessage } from '../models/core/ProofMessage'; import type { Feature } from '../../types/jsBridge'; @@ -37,12 +38,7 @@ export interface WalletAdapter { getStateInit(): Promise; /** Get the signed send transaction */ - getSignedSendTransaction( - input: TransactionRequest, - options?: { - fakeSignature: boolean; - }, - ): Promise; + getSignedSendTransaction(input: TransactionRequest, options?: SignedSendTransactionOptions): Promise; getSignedSignData( input: PreparedSignData, options?: { diff --git a/packages/walletkit/src/api/models/bridge/ConnectionRequestEvent.ts b/packages/walletkit/src/api/models/bridge/ConnectionRequestEvent.ts index c067db7e1..5e2a4e7a6 100644 --- a/packages/walletkit/src/api/models/bridge/ConnectionRequestEvent.ts +++ b/packages/walletkit/src/api/models/bridge/ConnectionRequestEvent.ts @@ -8,6 +8,7 @@ import type { DAppInfo } from '../core/DAppInfo'; import type { BridgeEvent } from './BridgeEvent'; +import type { EmbeddedRequest } from './EmbeddedRequest'; /** * Event containing a connection request from a dApp via TON Connect. @@ -21,6 +22,11 @@ export interface ConnectionRequestEvent extends BridgeEvent { * Preview information for UI display */ preview: ConnectionRequestEventPreview; + + /** + * Embedded request for user to approve along with connection + */ + embeddedRequest?: EmbeddedRequest; } /** diff --git a/packages/walletkit/src/api/models/bridge/EmbeddedRequest.ts b/packages/walletkit/src/api/models/bridge/EmbeddedRequest.ts new file mode 100644 index 000000000..1028d9361 --- /dev/null +++ b/packages/walletkit/src/api/models/bridge/EmbeddedRequest.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { SignDataPayload } from '../core/PreparedSignData'; +import type { TransactionRequest } from '../transactions/TransactionRequest'; +import type { SendTransactionRequestEvent } from './SendTransactionRequestEvent'; +import type { SignDataRequestEvent } from './SignDataRequestEvent'; +import type { SignMessageRequestEvent } from './SignMessageRequestEvent'; + +/** + * @discriminator method + */ +export type EmbeddedRequest = SendTransactionEmbeddedRequest | SignMessageEmbeddedRequest | SignDataEmbeddedRequest; + +export interface SendTransactionEmbeddedRequest { + method: 'sendTransaction'; + transactionRequest: TransactionRequest; +} + +export interface SignMessageEmbeddedRequest { + method: 'signMessage'; + transactionRequest: TransactionRequest; +} + +export interface SignDataEmbeddedRequest { + method: 'signData'; + payload: SignDataPayload; +} + +declare const embeddedConnectionResultBrand: unique symbol; + +/** + * Opaque type holding the pre-built connection approval response. + * Created by approveConnectRequest when an embedded request is present. + * Passed through to the action approval method which attaches the action result and sends it. + */ +export type EmbeddedConnectionResult = { readonly [embeddedConnectionResultBrand]: never }; + +/** + * @discriminator type + */ +export type EmbeddedRequestEvent = + | EmbeddedSendTransactionRequestEvent + | EmbeddedSignMessageRequestEvent + | EmbeddedSignDataRequestEvent; + +export interface EmbeddedSendTransactionRequestEvent extends SendTransactionRequestEvent { + type: 'sendTransaction'; + + /** + * @frozen + */ + connectionResult: EmbeddedConnectionResult; +} + +export interface EmbeddedSignMessageRequestEvent extends SignMessageRequestEvent { + type: 'signMessage'; + + /** + * @frozen + */ + connectionResult: EmbeddedConnectionResult; +} + +export interface EmbeddedSignDataRequestEvent extends SignDataRequestEvent { + type: 'signData'; + + /** + * @frozen + */ + connectionResult: EmbeddedConnectionResult; +} diff --git a/packages/walletkit/src/api/models/bridge/SignMessageApprovalResponse.ts b/packages/walletkit/src/api/models/bridge/SignMessageApprovalResponse.ts new file mode 100644 index 000000000..01379e17d --- /dev/null +++ b/packages/walletkit/src/api/models/bridge/SignMessageApprovalResponse.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { Base64String } from '../core/Primitives'; + +/** + * Response after user approves a sign-message request. + */ +export interface SignMessageApprovalResponse { + /** + * Signed internal message BoC (Bag of Cells) format, encoded in Base64. + * This is a signed internal message (internal opcode) that the dApp can relay via a third-party relayer. + */ + internalBoc: Base64String; +} diff --git a/packages/walletkit/src/api/models/bridge/SignMessageRequestEvent.ts b/packages/walletkit/src/api/models/bridge/SignMessageRequestEvent.ts new file mode 100644 index 000000000..c25947bd7 --- /dev/null +++ b/packages/walletkit/src/api/models/bridge/SignMessageRequestEvent.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { TransactionRequest } from '../transactions/TransactionRequest'; +import type { BridgeEvent } from './BridgeEvent'; +import type { SendTransactionRequestEventPreview } from './SendTransactionRequestEvent'; + +/** + * Event containing a sign-message (sign-only transaction) request from a dApp via TON Connect. + * The wallet signs the transaction using the internal opcode and returns the signed BoC + * without broadcasting it on-chain. + */ +export interface SignMessageRequestEvent extends BridgeEvent { + /** + * Preview information for UI display + */ + preview: SendTransactionRequestEventPreview; + + /** + * Raw transaction request data + */ + request: TransactionRequest; +} diff --git a/packages/walletkit/src/api/models/index.ts b/packages/walletkit/src/api/models/index.ts index 86668aecd..74f06f3cd 100644 --- a/packages/walletkit/src/api/models/index.ts +++ b/packages/walletkit/src/api/models/index.ts @@ -51,7 +51,20 @@ export type { SendTransactionRequestEvent, SendTransactionRequestEventPreview, } from './bridge/SendTransactionRequestEvent'; +export type { SignMessageApprovalResponse } from './bridge/SignMessageApprovalResponse'; +export type { SignMessageRequestEvent } from './bridge/SignMessageRequestEvent'; export type { RequestErrorEvent } from './bridge/RequestErrorEvent'; +export type { + EmbeddedRequestEvent, + EmbeddedSendTransactionRequestEvent, + EmbeddedSignMessageRequestEvent, + EmbeddedSignDataRequestEvent, + EmbeddedRequest, + EmbeddedConnectionResult, + SendTransactionEmbeddedRequest, + SignDataEmbeddedRequest, + SignMessageEmbeddedRequest, +} from './bridge/EmbeddedRequest'; export type { TONConnectSession } from './sessions/TONConnectSession'; // Jetton models @@ -95,6 +108,14 @@ export * from './transactions/Transaction'; export type { TransactionAddressMetadata, TransactionAddressMetadataEntry } from './transactions/TransactionMetadata'; export type { TransactionTraceMoneyFlow as TransactionMoneyFlow } from './transactions/TransactionTraceMoneyFlow'; export type { TransactionRequest, TransactionRequestMessage } from './transactions/TransactionRequest'; +export type { SignedSendTransactionOptions } from './transactions/SignedSendTransactionOptions'; +export type { + StructuredItem, + StructuredItemType, + TonTransferItem, + JettonTransferItem, + NftTransferItem, +} from './transactions/StructuredItem'; export * from './transactions/TransactionTrace'; export type { TransactionEmulatedPreview } from './transactions/emulation/TransactionEmulatedPreview'; export type { TransactionEmulatedTrace } from './transactions/emulation/TransactionEmulatedTrace'; diff --git a/packages/walletkit/src/api/models/transactions/SignedSendTransactionOptions.ts b/packages/walletkit/src/api/models/transactions/SignedSendTransactionOptions.ts new file mode 100644 index 000000000..0e7453ca2 --- /dev/null +++ b/packages/walletkit/src/api/models/transactions/SignedSendTransactionOptions.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +export interface SignedSendTransactionOptions { + fakeSignature?: boolean; + /** Use internal message opcode (0x73696e74) instead of external (0x7369676e) for gasless relaying */ + internal?: boolean; +} diff --git a/packages/walletkit/src/api/models/transactions/StructuredItem.ts b/packages/walletkit/src/api/models/transactions/StructuredItem.ts new file mode 100644 index 000000000..104b7eeaa --- /dev/null +++ b/packages/walletkit/src/api/models/transactions/StructuredItem.ts @@ -0,0 +1,53 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +// Structured item types for sendTransaction/signMessage with items instead of raw messages + +import type { Base64String } from '../core/Primitives'; +import type { ExtraCurrencies } from '../core/ExtraCurrencies'; + +export type StructuredItemType = 'ton' | 'jetton' | 'nft'; + +/** + * @discriminator type + */ +export type StructuredItem = TonTransferItem | JettonTransferItem | NftTransferItem; + +export interface TonTransferItem { + type: 'ton'; + address: string; + amount: string; + payload?: Base64String; + stateInit?: Base64String; + extraCurrency?: ExtraCurrencies; +} + +export interface JettonTransferItem { + type: 'jetton'; + master: string; + destination: string; + amount: string; + attachAmount?: string; + queryId?: string; + responseDestination?: string; + customPayload?: Base64String; + forwardAmount?: string; + forwardPayload?: Base64String; +} + +export interface NftTransferItem { + type: 'nft'; + nftAddress: string; + newOwner: string; + attachAmount?: string; + queryId?: string; + responseDestination?: string; + customPayload?: Base64String; + forwardAmount?: string; + forwardPayload?: Base64String; +} diff --git a/packages/walletkit/src/api/models/transactions/TransactionRequest.ts b/packages/walletkit/src/api/models/transactions/TransactionRequest.ts index 53a1d5cf5..b88534562 100644 --- a/packages/walletkit/src/api/models/transactions/TransactionRequest.ts +++ b/packages/walletkit/src/api/models/transactions/TransactionRequest.ts @@ -11,9 +11,11 @@ import type { Network } from '../core/Network'; import type { Base64String } from '../core/Primitives'; import type { SendMode } from '../core/SendMode'; import type { TokenAmount } from '../core/TokenAmount'; +import type { StructuredItem } from './StructuredItem'; /** * Request to send a transaction on the TON blockchain. + * Contains `messages` or `items`. If items are present, but messages are not — wallet app is responsible for resolving items into messages. */ export interface TransactionRequest { /** @@ -21,6 +23,12 @@ export interface TransactionRequest { */ messages: TransactionRequestMessage[]; + /** + * List of structured items (ton/jetton/nft) as an alternative to raw messages. + * When present, the wallet app is responsible for resolving items into messages. + */ + items?: StructuredItem[]; + /** * Network to execute the transaction on */ diff --git a/packages/walletkit/src/contracts/v4r2/WalletV4R2Adapter.ts b/packages/walletkit/src/contracts/v4r2/WalletV4R2Adapter.ts index a3e6f79b7..4cbe84051 100644 --- a/packages/walletkit/src/contracts/v4r2/WalletV4R2Adapter.ts +++ b/packages/walletkit/src/contracts/v4r2/WalletV4R2Adapter.ts @@ -45,6 +45,7 @@ import type { UserFriendlyAddress, Hex, Base64String, + SignedSendTransactionOptions, } from '../../api/models'; const log = globalLogger.createChild('WalletV4R2Adapter'); @@ -142,8 +143,11 @@ export class WalletV4R2Adapter implements WalletAdapter { async getSignedSendTransaction( input: TransactionRequest, - _options: { fakeSignature: boolean }, + options?: SignedSendTransactionOptions, ): Promise { + if (options?.internal) { + throw new Error('WalletV4R2 does not support internal message signing (gasless). Use WalletV5R1.'); + } if (input.messages.length === 0) { throw new Error('Ledger does not support empty messages'); } @@ -165,9 +169,13 @@ export class WalletV4R2Adapter implements WalletAdapter { try { const messages: MessageRelaxed[] = input.messages.map((m) => { let bounce = true; - const parsedAddress = Address.parseFriendly(m.address); - if (parsedAddress.isBounceable === false) { - bounce = false; + try { + const parsedAddress = Address.parseFriendly(m.address); + if (parsedAddress.isBounceable === false) { + bounce = false; + } + } catch { + // raw address — no bounceable flag, keep default true } return internal({ @@ -284,11 +292,11 @@ export class WalletV4R2Adapter implements WalletAdapter { { name: 'SendTransaction', maxMessages: 4, + extraCurrencySupported: true, + itemTypes: ['ton', 'jetton', 'nft'], }, - { - name: 'SignData', - types: ['binary', 'cell', 'text'], - }, - ]; + { name: 'SignData', types: ['text', 'binary', 'cell'] }, + { name: 'EmbeddedRequest' }, + ] as Feature[]; } } diff --git a/packages/walletkit/src/contracts/w5/WalletV5R1Adapter.ts b/packages/walletkit/src/contracts/w5/WalletV5R1Adapter.ts index 10144f0c1..67511cde1 100644 --- a/packages/walletkit/src/contracts/w5/WalletV5R1Adapter.ts +++ b/packages/walletkit/src/contracts/w5/WalletV5R1Adapter.ts @@ -8,7 +8,7 @@ // WalletV5R1 adapter that implements WalletInterface -import type { SignatureDomain, StateInit } from '@ton/core'; +import type { CommonMessageInfoInternal, SignatureDomain, StateInit } from '@ton/core'; import { Address, beginCell, @@ -18,6 +18,7 @@ import { SendMode, signatureDomainPrefix, storeMessage, + storeMessageRelaxed, storeStateInit, } from '@ton/core'; import { external, internal } from '@ton/core'; @@ -36,6 +37,7 @@ import { CreateTonProofMessageBytes } from '../../utils/tonProof'; import type { WalletId } from '../../utils/walletId'; import { createWalletId } from '../../utils/walletId'; import type { WalletAdapter, WalletSigner } from '../../api/interfaces'; +import type { SignedSendTransactionOptions } from '../../api/models'; import type { Network, PreparedSignData, @@ -169,14 +171,18 @@ export class WalletV5R1Adapter implements WalletAdapter { async getSignedSendTransaction( input: TransactionRequest, - options: { fakeSignature: boolean }, + options?: SignedSendTransactionOptions, ): Promise { const actions = packActionsList( input.messages.map((m) => { let bounce = true; - const parsedAddress = Address.parseFriendly(m.address); - if (parsedAddress.isBounceable === false) { - bounce = false; + try { + const parsedAddress = Address.parseFriendly(m.address); + if (parsedAddress.isBounceable === false) { + bounce = false; + } + } catch { + // raw address — no bounceable flag, keep default true } const msg = internal({ @@ -217,7 +223,7 @@ export class WalletV5R1Adapter implements WalletAdapter { }), ); - const createBodyOptions: { validUntil: number | undefined; fakeSignature: boolean } = { + const createBodyOptions: { validUntil: number | undefined; fakeSignature?: boolean; internal?: boolean } = { ...options, validUntil: undefined, }; @@ -252,6 +258,27 @@ export class WalletV5R1Adapter implements WalletAdapter { const transfer = await this.createBodyV5(seqno, walletId, actions, createBodyOptions); + if (options?.internal) { + // For gasless relaying, the signed body (auth_signed_internal opcode) must be + // delivered to the wallet via an internal message from a relayer contract. + const msg = internal({ + to: this.walletContract.address, + value: 0n, + body: transfer, + bounce: false, + }); + msg.info = msg.info as CommonMessageInfoInternal; + msg.info.createdLt = 0n; + msg.info.createdAt = 0; + msg.info.ihrFee = 0n; + msg.info.forwardFee = 0n; + msg.info.ihrDisabled = false; + msg.info.bounce = false; + msg.info.bounced = false; + msg.info.src = new Address(0, Buffer.alloc(32)); + return beginCell().store(storeMessageRelaxed(msg)).endCell().toBoc().toString('base64') as Base64String; + } + const ext = external({ to: this.walletContract.address, init: this.walletContract.init, @@ -325,15 +352,24 @@ export class WalletV5R1Adapter implements WalletAdapter { seqno: number, walletId: bigint, actionsList: Cell, - options: { validUntil: number | undefined; fakeSignature: boolean }, + options: { validUntil: number | undefined; fakeSignature?: boolean; internal?: boolean }, ) { + // Opcodes defined in the WalletV5R1 contract spec, confirmed in @ton/ton WalletContractV5R1.js const Opcodes = { - auth_signed: 0x7369676e, + auth_signed: 0x7369676e, // external auth ("sign") + auth_signed_internal: 0x73696e74, // internal auth ("sint") — used for gasless relaying }; + // Use internal opcode for gasless relaying (signOnly / signMsg intent) + const opcode = options.internal ? Opcodes.auth_signed_internal : Opcodes.auth_signed; + log.debug('createBodyV5 signing with opcode', { + internal: options.internal, + opcode: `0x${opcode.toString(16)}`, + }); + const expireAt = options.validUntil ?? Math.floor(Date.now() / 1000) + 300; const payload = beginCell() - .storeUint(Opcodes.auth_signed, 32) + .storeUint(opcode, 32) .storeUint(walletId, 32) .storeUint(expireAt, 32) .storeUint(seqno, 32) // seqno @@ -363,14 +399,23 @@ export class WalletV5R1Adapter implements WalletAdapter { getSupportedFeatures(): Feature[] | undefined { return [ + 'SendTransaction', { name: 'SendTransaction', maxMessages: 255, + extraCurrencySupported: true, + itemTypes: ['ton', 'jetton', 'nft'], + }, + { name: 'SignData', types: ['text', 'binary', 'cell'] }, + { + name: 'SignMessage', + maxMessages: 255, + extraCurrencySupported: true, + itemTypes: ['ton', 'jetton', 'nft'], }, { - name: 'SignData', - types: ['binary', 'cell', 'text'], + name: 'EmbeddedRequest', }, - ]; + ] as Feature[]; } } diff --git a/packages/walletkit/src/core/EventRouter.ts b/packages/walletkit/src/core/EventRouter.ts index 06ab8b668..b02edc419 100644 --- a/packages/walletkit/src/core/EventRouter.ts +++ b/packages/walletkit/src/core/EventRouter.ts @@ -12,6 +12,7 @@ import type { RawBridgeEvent, EventHandler, EventCallback, EventType } from '../ import { ConnectHandler } from '../handlers/ConnectHandler'; import { TransactionHandler } from '../handlers/TransactionHandler'; import { SignDataHandler } from '../handlers/SignDataHandler'; +import { SignMessageHandler } from '../handlers/SignMessageHandler'; import { DisconnectHandler } from '../handlers/DisconnectHandler'; import { validateBridgeEvent } from '../validation/events'; import { globalLogger } from './Logger'; @@ -26,6 +27,7 @@ import type { RequestErrorEvent, DisconnectionEvent, SignDataRequestEvent, + SignMessageRequestEvent, ConnectionRequestEvent, } from '../api/models'; import type { TonWalletKitOptions } from '../types/config'; @@ -40,6 +42,7 @@ export class EventRouter { private connectRequestCallback: EventCallback | undefined = undefined; private transactionRequestCallback: EventCallback | undefined = undefined; private signDataRequestCallback: EventCallback | undefined = undefined; + private signMessageRequestCallback: EventCallback | undefined = undefined; private disconnectCallback: EventCallback | undefined = undefined; private errorCallback: EventCallback | undefined = undefined; @@ -107,6 +110,10 @@ export class EventRouter { this.signDataRequestCallback = callback; } + onSignMessageRequest(callback: EventCallback): void { + this.signMessageRequestCallback = callback; + } + onDisconnect(callback: EventCallback): void { this.disconnectCallback = callback; } @@ -130,6 +137,10 @@ export class EventRouter { this.signDataRequestCallback = undefined; } + removeSignMessageRequestCallback(): void { + this.signMessageRequestCallback = undefined; + } + removeDisconnectCallback(): void { this.disconnectCallback = undefined; } @@ -145,6 +156,7 @@ export class EventRouter { this.connectRequestCallback = undefined; this.transactionRequestCallback = undefined; this.signDataRequestCallback = undefined; + this.signMessageRequestCallback = undefined; this.disconnectCallback = undefined; this.errorCallback = undefined; } @@ -169,6 +181,14 @@ export class EventRouter { this.sessionManager, this.analyticsManager, ), + new SignMessageHandler( + this.notifySignMessageRequestCallbacks.bind(this), + this.config, + this.eventEmitter, + this.walletManager, + this.sessionManager, + this.analyticsManager, + ), new DisconnectHandler(this.notifyDisconnectCallbacks.bind(this), this.sessionManager), ]; } @@ -194,6 +214,13 @@ export class EventRouter { return await this.signDataRequestCallback?.(event); } + /** + * Notify sign message request callbacks + */ + private async notifySignMessageRequestCallbacks(event: SignMessageRequestEvent): Promise { + return await this.signMessageRequestCallback?.(event); + } + /** * Notify disconnect callbacks */ @@ -223,6 +250,9 @@ export class EventRouter { if (this.signDataRequestCallback) { enabledTypes.push('signData'); } + if (this.signMessageRequestCallback) { + enabledTypes.push('signMessage'); + } if (this.disconnectCallback) { enabledTypes.push('disconnect'); } diff --git a/packages/walletkit/src/core/EventStore.ts b/packages/walletkit/src/core/EventStore.ts index c66b4c6fd..15f7f96a2 100644 --- a/packages/walletkit/src/core/EventStore.ts +++ b/packages/walletkit/src/core/EventStore.ts @@ -398,6 +398,8 @@ export class StorageEventStore implements EventStore { return 'sendTransaction'; case 'signData': return 'signData'; + case 'signMessage': + return 'signMessage'; case 'disconnect': return 'disconnect'; case 'restoreConnection': diff --git a/packages/walletkit/src/core/JettonsManager.ts b/packages/walletkit/src/core/JettonsManager.ts index 5d2aa3e50..2d7df42e3 100644 --- a/packages/walletkit/src/core/JettonsManager.ts +++ b/packages/walletkit/src/core/JettonsManager.ts @@ -22,7 +22,25 @@ import type { Network } from '../api/models'; import { asMaybeAddressFriendly } from '../utils'; const log = globalLogger.createChild('JettonsManager'); +const TON_ADDRESS = 'TON'; +function isTonAddress(address: string): boolean { + return address.toLowerCase() === 'ton'; +} + +const TON_INFO: JettonInfo = { + address: TON_ADDRESS, + name: 'TON', + symbol: 'TON', + description: 'The Open Network native token', + decimals: 9, + totalSupply: '5000000000000000000', + // image: 'https://asset.ston.fi/img/EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c/ee9fb21d17bc8d75c2a5f7b5f5f62d2bacec6b128f58b63cb841e98f7b74c4fc', + verification: { + verified: true, + source: 'manual' as const, + }, +}; /** * Creates a cache key that includes the network ID */ @@ -73,20 +91,8 @@ export class JettonsManager implements JettonsAPI { * Add TON native token to cache for a specific network */ private addTonToCache(network: Network): void { - const cacheKey = createCacheKey(network, 'TON'); - this.cache.set(cacheKey, { - address: 'TON', - name: 'TON', - symbol: 'TON', - description: 'The Open Network native token', - decimals: 9, - totalSupply: '5000000000000000000', - image: 'https://asset.ston.fi/img/EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c/ee9fb21d17bc8d75c2a5f7b5f5f62d2bacec6b128f58b63cb841e98f7b74c4fc', - verification: { - verified: true, - source: 'manual' as const, - }, - }); + const cacheKey = createCacheKey(network, TON_ADDRESS); + this.cache.set(cacheKey, TON_INFO); } /** @@ -97,6 +103,10 @@ export class JettonsManager implements JettonsAPI { async getJettonInfo(jettonAddress: string, network: Network): Promise { const targetNetwork = network; + if (isTonAddress(jettonAddress)) { + return TON_INFO; + } + try { const cacheKey = this.normalizedCacheKey(targetNetwork, jettonAddress); const cachedInfo = this.cache.get(cacheKey); @@ -290,8 +300,8 @@ export class JettonsManager implements JettonsAPI { * Normalize jetton address for consistent caching */ private normalizedCacheKey(network: Network, address: string): string { - if (address === 'TON') { - return createCacheKey(network, address); + if (isTonAddress(address)) { + return createCacheKey(network, TON_ADDRESS); } return createCacheKey(network, Address.parse(address).toString()); } @@ -311,7 +321,7 @@ export class JettonsManager implements JettonsAPI { */ validateJettonAddress(address: string): boolean { try { - if (address === 'TON') { + if (isTonAddress(address)) { return true; } diff --git a/packages/walletkit/src/core/RequestProcessor.ts b/packages/walletkit/src/core/RequestProcessor.ts index 3242061c1..88c1aeb64 100644 --- a/packages/walletkit/src/core/RequestProcessor.ts +++ b/packages/walletkit/src/core/RequestProcessor.ts @@ -16,6 +16,8 @@ import type { SendTransactionRpcResponseSuccess, SignDataRpcResponseError, SignDataRpcResponseSuccess, + SignMessageRpcResponseError, + SignMessageRpcResponseSuccess, SignDataPayload as TonConnectSignDataPayload, TonProofItemReply, } from '@tonconnect/protocol'; @@ -24,6 +26,7 @@ import { SEND_TRANSACTION_ERROR_CODES, SessionCrypto, SIGN_DATA_ERROR_CODES, + SIGN_MESSAGE_ERROR_CODES, } from '@tonconnect/protocol'; import { getSecureRandomBytes } from '@ton/crypto'; @@ -42,19 +45,45 @@ import type { SignDataPayload, SendTransactionRequestEvent, SignDataRequestEvent, + SignMessageRequestEvent, ConnectionRequestEvent, SendTransactionApprovalResponse, SignDataApprovalResponse, + SignMessageApprovalResponse, Base64String, ConnectionApprovalResponse, ConnectionApprovalProof, + TransactionEmulatedPreview, + SignDataPreview, + BridgeEvent, + TONConnectSession, + EmbeddedConnectionResult, + EmbeddedRequestEvent, + EmbeddedSendTransactionRequestEvent, + EmbeddedSignDataRequestEvent, + EmbeddedSignMessageRequestEvent, } from '../api/models'; import { PrepareSignData } from '../utils/signData/sign'; +import { validateBOC } from '../validation/transaction'; import type { Wallet } from '../api/interfaces'; import type { Analytics, AnalyticsManager } from '../analytics'; +import { createTransactionPreviewIfPossible } from '../utils'; +import { + checkTransactionRequestItems, + getClientForWallet, + getWalletAddressFromEvent, + getWalletFromEvent, + validateTransactionRequestForWallet, +} from '../utils/events'; const log = globalLogger.createChild('RequestProcessor'); +function hasConnectionResult( + event: BridgeEvent, +): event is BridgeEvent & { connectionResult: EmbeddedConnectionResult } { + return 'connectionResult' in event; +} + /** * Handles approval and rejection of various request types */ @@ -71,57 +100,26 @@ export class RequestProcessor { this.analytics = analyticsManager?.scoped(); } - /** - * Helper to get wallet from event, supporting both walletId and walletAddress - */ - private getWalletFromEvent(event: { walletId?: string }): Wallet | undefined { - if (event.walletId) { - return this.walletManager.getWallet(event.walletId); - } - return undefined; - } - - /** - * Helper to get wallet address from event - */ - private getWalletAddressFromEvent(event: { walletId?: string; walletAddress?: string }): string | undefined { - if (event.walletAddress) { - return event.walletAddress; - } - if (event.walletId) { - return this.walletManager.getWallet(event.walletId)?.getAddress(); - } - return undefined; - } - /** * Process connect request approval */ - async approveConnectRequest(event: ConnectionRequestEvent, response?: ConnectionApprovalResponse): Promise { - try { - // If event is ConnectionRequestEvent, we need to create approval ourself - const walletId = event.walletId; - - if (!walletId) { - const error = new WalletKitError( - ERROR_CODES.WALLET_REQUIRED, - 'Wallet is required for connect request approval', - undefined, - { eventId: event.id }, - ); - throw error; + async approveConnectRequest( + event: ConnectionRequestEvent, + response?: ConnectionApprovalResponse, + ): Promise { + if (event.embeddedRequest) { + switch (event.embeddedRequest.method) { + case 'sendTransaction': + return this.approveConnectRequestSendTransactionEmbeddedRequest(event, response); + case 'signMessage': + return this.approveConnectRequestSignMessageEmbeddedRequest(event, response); + case 'signData': + return this.approveConnectRequestSignDataEmbeddedRequest(event, response); } + } - const wallet = this.getWalletFromEvent(event); - if (!wallet) { - const error = new WalletKitError( - ERROR_CODES.WALLET_NOT_FOUND, - 'Wallet not found for connect request', - undefined, - { walletId, eventId: event.id }, - ); - throw error; - } + try { + const wallet = this.validateWallet(event); // Create session for this connection' const newSession = await this.sessionManager.createSession( @@ -181,6 +179,231 @@ export class RequestProcessor { } } + private async getTransactionRequestAndPreview( + event: ConnectionRequestEvent, + ): Promise<{ request: TransactionRequest; preview: TransactionEmulatedPreview | undefined }> { + if (!event.embeddedRequest) { + throw new WalletKitError( + ERROR_CODES.INVALID_REQUEST_EVENT, + 'Embedded request is required for send transaction', + ); + } + + if (event.embeddedRequest.method !== 'sendTransaction' && event.embeddedRequest.method !== 'signMessage') { + throw new WalletKitError( + ERROR_CODES.INVALID_REQUEST_EVENT, + 'Incorrect embedded request method for send transaction or sign message', + ); + } + + const wallet = this.validateWallet(event); + const transactionRequest = event.embeddedRequest.transactionRequest; + const validation = validateTransactionRequestForWallet(transactionRequest, wallet, event.isLocal, false); + if (!validation.isValid) { + throw new WalletKitError( + ERROR_CODES.VALIDATION_ERROR, + `Invalid embedded transaction request: ${validation.errors.join(', ')}`, + undefined, + { errors: validation.errors }, + ); + } + + const request = await checkTransactionRequestItems(transactionRequest, wallet); + const preview = await createTransactionPreviewIfPossible( + this.walletKitOptions, + wallet.client, + request, + wallet, + { + mode: event.embeddedRequest.method === 'signMessage' ? 'sign' : 'send', + }, + ); + + return { + request, + preview, + }; + } + + private async approveConnectRequestSendTransactionEmbeddedRequest( + event: ConnectionRequestEvent, + response?: ConnectionApprovalResponse, + ): Promise { + if (!event.embeddedRequest || event.embeddedRequest.method !== 'sendTransaction') { + throw new WalletKitError( + ERROR_CODES.INVALID_REQUEST_EVENT, + 'Incorrect embedded request method for send transaction', + ); + } + + const { request, preview } = await this.getTransactionRequestAndPreview(event); + const connectionResult = await this.createConnectApprovalResponse(event, response?.proof); + await this.createSessionForEmbeddedRequest(event); + + return { + ...event, + type: 'sendTransaction', + preview: { + data: preview, + }, + request, + connectionResult: connectionResult.result as unknown as EmbeddedConnectionResult, + }; + } + + private async approveConnectRequestSignMessageEmbeddedRequest( + event: ConnectionRequestEvent, + response?: ConnectionApprovalResponse, + ): Promise { + if (!event.embeddedRequest || event.embeddedRequest.method !== 'signMessage') { + throw new WalletKitError( + ERROR_CODES.INVALID_REQUEST_EVENT, + 'Incorrect embedded request method for sign message', + ); + } + + const { request, preview } = await this.getTransactionRequestAndPreview(event); + const connectionResult = await this.createConnectApprovalResponse(event, response?.proof); + await this.createSessionForEmbeddedRequest(event); + + return { + ...event, + type: 'signMessage', + preview: { + data: preview, + }, + request, + connectionResult: connectionResult.result as unknown as EmbeddedConnectionResult, + }; + } + + private async approveConnectRequestSignDataEmbeddedRequest( + event: ConnectionRequestEvent, + response?: ConnectionApprovalResponse, + ): Promise { + if (!event.embeddedRequest || event.embeddedRequest.method !== 'signData') { + throw new WalletKitError( + ERROR_CODES.INVALID_REQUEST_EVENT, + 'Incorrect embedded request method for sign data', + ); + } + + const embeddedRequest = event.embeddedRequest; + const connectionResult = await this.createConnectApprovalResponse(event, response?.proof); + const session = await this.createSessionForEmbeddedRequest(event); + + return { + ...event, + domain: session.domain, + type: 'signData', + payload: embeddedRequest.payload, + preview: { + dAppInfo: event.dAppInfo ?? {}, + data: this.createSignDataPreview(embeddedRequest.payload), + }, + connectionResult: connectionResult.result as unknown as EmbeddedConnectionResult, + }; + } + + /** + * Validate wallet exists for an embedded request. Shared by all embedded request handlers. + */ + private validateWallet(event: ConnectionRequestEvent): Wallet { + const walletId = event.walletId; + if (!walletId) { + throw new WalletKitError( + ERROR_CODES.WALLET_REQUIRED, + 'Wallet is required for embedded request approval', + undefined, + { + eventId: event.id, + }, + ); + } + + const wallet = getWalletFromEvent(this.walletManager, event); + if (!wallet) { + throw new WalletKitError(ERROR_CODES.WALLET_NOT_FOUND, 'Wallet not found for embedded request', undefined, { + walletId, + eventId: event.id, + }); + } + + return wallet; + } + + /** + * Create sign data preview from payload. Reuses the same logic as SignDataHandler. + */ + private createSignDataPreview(payload: SignDataPayload): SignDataPreview { + switch (payload.data.type) { + case 'text': + return { type: 'text', value: { content: payload.data.value.content } }; + case 'binary': + return { type: 'binary', value: { content: payload.data.value.content } }; + case 'cell': + return { + type: 'cell', + value: { schema: payload.data.value.schema, content: payload.data.value.content }, + }; + } + } + + /** + * Create session and bridge session for an embedded request event before sending the combined response. + * Mirrors the session creation in approveConnectRequest. + */ + private async createSessionForEmbeddedRequest(event: BridgeEvent): Promise { + const wallet = getWalletFromEvent(this.walletManager, event); + if (!wallet) { + throw new WalletKitError( + ERROR_CODES.WALLET_NOT_FOUND, + 'Wallet not found for embedded request session creation', + ); + } + + const newSession = await this.sessionManager.createSession( + event.from || (await getSecureRandomBytes(32)).toString('hex'), + { + name: event.dAppInfo?.name || '', + url: event.dAppInfo?.url || '', + iconUrl: event.dAppInfo?.iconUrl || '', + description: event.dAppInfo?.description || '', + }, + wallet, + event.isJsBridge ?? false, + ); + await this.bridgeManager.createSession(newSession.sessionId); + return newSession; + } + + private async sendBridgeMessage( + event: BridgeEvent, + result: + | SendTransactionRpcResponseSuccess + | SignMessageRpcResponseSuccess + | SignDataRpcResponseSuccess + | undefined, + error: SendTransactionRpcResponseError | SignMessageRpcResponseError | SignDataRpcResponseError | undefined, + ) { + if (!hasConnectionResult(event)) { + await this.bridgeManager.sendResponse(event, error ?? result); + return; + } + + if (error) { + const connResult = event.connectionResult as unknown as ConnectEventSuccess; + const response = { ...connResult, response: { error: error.error } as ConnectEventSuccess['response'] }; + await this.bridgeManager.sendResponse(event, response); + return; + } + + const connResult = event.connectionResult as unknown as ConnectEventSuccess; + const response = { ...connResult, response: { result: result?.result } as ConnectEventSuccess['response'] }; + await this.bridgeManager.sendResponse(event, response); + return; + } + /** * Process connect request rejection */ @@ -263,7 +486,8 @@ export class RequestProcessor { result: response.signedBoc, id: event.id || '', }; - await this.bridgeManager.sendResponse(event, tonConnectResponse); + await this.sendBridgeMessage(event, tonConnectResponse, undefined); + this.sendTransactionAnalytics(event, response.signedBoc); return response; } else { @@ -271,17 +495,18 @@ export class RequestProcessor { if (!this.walletKitOptions.dev?.disableNetworkSend) { // Get the client for the wallet's network - const client = this.getClientForWallet(event.walletId); + const client = getClientForWallet(this.walletManager, event); await CallForSuccess(() => client.sendBoc(signedBoc)); } // Send approval response - const response: SendTransactionRpcResponseSuccess = { + const transactionResponse: SendTransactionRpcResponseSuccess = { result: signedBoc, id: event.id || '', }; - await this.bridgeManager.sendResponse(event, response); + await this.sendBridgeMessage(event, transactionResponse, undefined); + this.sendTransactionAnalytics(event, signedBoc); return { signedBoc }; } @@ -304,7 +529,7 @@ export class RequestProcessor { private sendTransactionAnalytics(event: SendTransactionRequestEvent, signedBoc: string): void { if (!this.analytics) return; - const wallet = this.getWalletFromEvent(event); + const wallet = getWalletFromEvent(this.walletManager, event); this.analytics.emitWalletTransactionSent({ trace_id: event.traceId, @@ -336,8 +561,9 @@ export class RequestProcessor { id: event.id, }; - await this.bridgeManager.sendResponse(event, response); - const wallet = this.getWalletFromEvent(event); + await this.sendBridgeMessage(event, undefined, response); + + const wallet = getWalletFromEvent(this.walletManager, event); if (this.analytics) { const sessionData = event.from ? await this.sessionManager.getSession(event.from) : undefined; @@ -360,6 +586,77 @@ export class RequestProcessor { } } + /** + * Process sign-message (sign-only transaction) request approval. + * Signs using the internal opcode and returns the BoC without broadcasting. + */ + async approveSignMessageRequest( + event: SignMessageRequestEvent, + response?: SignMessageApprovalResponse, + ): Promise { + try { + if (response) { + const bocValidation = validateBOC(response.internalBoc); + if (!bocValidation.isValid) { + throw new WalletKitError( + ERROR_CODES.VALIDATION_ERROR, + `Invalid internalBoc: ${bocValidation.errors.join(', ')}`, + ); + } + + const tonConnectResponse = { + result: { internalBoc: response.internalBoc }, + id: event.id || '', + }; + await this.sendBridgeMessage(event, tonConnectResponse, undefined); + + return response; + } else { + const wallet = getWalletFromEvent(this.walletManager, event); + if (!wallet) { + throw new WalletKitError( + ERROR_CODES.WALLET_NOT_FOUND, + 'Wallet not found for sign message signing', + undefined, + { eventId: event.id }, + ); + } + const internalBoc = await wallet.getSignedSendTransaction(event.request, { internal: true }); + const actionResult = { internalBoc: internalBoc }; + + const tonConnectResponse = { + result: actionResult, + id: event.id || '', + }; + await this.sendBridgeMessage(event, tonConnectResponse, undefined); + + return { internalBoc }; + } + } catch (error) { + log.error('Failed to approve sign message request', { error }); + throw error; + } + } + + /** + * Process sign-message request rejection + */ + async rejectSignMessageRequest(event: SignMessageRequestEvent, reason?: string): Promise { + try { + const response = { + error: { + code: SIGN_MESSAGE_ERROR_CODES.USER_REJECTS_ERROR, + message: reason || 'User rejected sign message request', + }, + id: event.id, + }; + await this.sendBridgeMessage(event, undefined, response); + } catch (error) { + log.error('Failed to reject sign message request', { error }); + throw error; + } + } + /** * Process sign data request approval */ @@ -369,7 +666,7 @@ export class RequestProcessor { ): Promise { try { if (response) { - const wallet = this.getWalletFromEvent(event); + const wallet = getWalletFromEvent(this.walletManager, event); if (!wallet) { const error = new WalletKitError( @@ -381,18 +678,19 @@ export class RequestProcessor { throw error; } + const signDataResult = { + signature: HexToBase64(response.signature), + address: Address.parse(wallet.getAddress()).toRawString(), + timestamp: response.timestamp, + domain: response.domain, + payload: toTonConnectSignDataPayload(event.payload), + }; + const tonConnectResponse: SignDataRpcResponseSuccess = { id: event.id || '', - result: { - signature: HexToBase64(response.signature), - address: Address.parse(wallet.getAddress()).toRawString(), - timestamp: response.timestamp, - domain: response.domain, - payload: toTonConnectSignDataPayload(event.payload), - }, + result: signDataResult, }; - - await this.bridgeManager.sendResponse(event, tonConnectResponse); + await this.sendBridgeMessage(event, tonConnectResponse, undefined); if (this.analytics) { const sessionData = event.from ? await this.sessionManager.getSession(event.from) : undefined; @@ -400,13 +698,17 @@ export class RequestProcessor { this.analytics.emitWalletSignDataAccepted({ wallet_id: sessionData?.publicKey, trace_id: event.traceId, - network_id: wallet?.getNetwork().chainId, + dapp_name: event.dAppInfo?.name, + origin_url: event.dAppInfo?.url, + network_id: wallet.getNetwork().chainId, client_id: event.from, }); this.analytics.emitWalletSignDataSent({ wallet_id: sessionData?.publicKey, trace_id: event.traceId, - network_id: wallet?.getNetwork().chainId, + dapp_name: event.dAppInfo?.name, + origin_url: event.dAppInfo?.url, + network_id: wallet.getNetwork().chainId, client_id: event.from, }); } @@ -424,7 +726,7 @@ export class RequestProcessor { } const walletId = event.walletId; - const walletAddress = this.getWalletAddressFromEvent(event); + const walletAddress = getWalletAddressFromEvent(this.walletManager, event); if (!walletId && !walletAddress) { const error = new WalletKitError( @@ -436,7 +738,7 @@ export class RequestProcessor { throw error; } - const wallet = this.getWalletFromEvent(event); + const wallet = getWalletFromEvent(this.walletManager, event); if (!wallet) { const error = new WalletKitError( ERROR_CODES.WALLET_NOT_FOUND, @@ -464,18 +766,19 @@ export class RequestProcessor { const signatureBase64 = HexToBase64(signature); // Send approval response + const signDataResult = { + signature: signatureBase64, + address: Address.parse(signData.address).toRawString(), + timestamp: signData.timestamp, + domain: signData.domain, + payload: toTonConnectSignDataPayload(signData.payload), + }; + const response: SignDataRpcResponseSuccess = { id: event.id, - result: { - signature: signatureBase64, - address: Address.parse(signData.address).toRawString(), - timestamp: signData.timestamp, - domain: signData.domain, - payload: toTonConnectSignDataPayload(signData.payload), - }, + result: signDataResult, }; - - await this.bridgeManager.sendResponse(event, response); + await this.sendBridgeMessage(event, response, undefined); if (this.analytics) { const sessionData = event.from ? await this.sessionManager.getSession(event.from) : undefined; @@ -535,8 +838,9 @@ export class RequestProcessor { id: event.id, }; - await this.bridgeManager.sendResponse(event, response); - const wallet = this.getWalletFromEvent(event); + await this.sendBridgeMessage(event, undefined, response); + + const wallet = getWalletFromEvent(this.walletManager, event); if (this.analytics) { const sessionData = event.from ? await this.sessionManager.getSession(event.from) : undefined; @@ -566,7 +870,7 @@ export class RequestProcessor { proof?: ConnectionApprovalProof, ): Promise<{ result: ConnectEventSuccess }> { const walletId = event.walletId; - const walletAddress = this.getWalletAddressFromEvent(event); + const walletAddress = getWalletAddressFromEvent(this.walletManager, event); if (!walletId && !walletAddress) { throw new WalletKitError( @@ -576,7 +880,7 @@ export class RequestProcessor { { eventId: event.id }, ); } - const wallet = this.getWalletFromEvent(event); + const wallet = getWalletFromEvent(this.walletManager, event); if (!wallet) { throw new WalletKitError( ERROR_CODES.WALLET_NOT_FOUND, @@ -631,7 +935,7 @@ export class RequestProcessor { */ private async signTransaction(event: SendTransactionRequestEvent): Promise { const walletId = event.walletId; - const walletAddress = this.getWalletAddressFromEvent(event); + const walletAddress = getWalletAddressFromEvent(this.walletManager, event); if (!walletId && !walletAddress) { throw new WalletKitError( @@ -641,7 +945,7 @@ export class RequestProcessor { { eventId: event.id }, ); } - const wallet = this.getWalletFromEvent(event); + const wallet = getWalletFromEvent(this.walletManager, event); if (!wallet) { throw new WalletKitError( ERROR_CODES.WALLET_NOT_FOUND, @@ -666,23 +970,6 @@ export class RequestProcessor { return await signTransactionInternal(wallet, event.request); } - - /** - * Get API client for a wallet's network - * Uses the wallet's network to get the appropriate client from NetworkManager - */ - private getClientForWallet(walletId: string | undefined) { - if (!walletId) { - throw new WalletKitError(ERROR_CODES.WALLET_REQUIRED, 'Wallet address is required to get API client'); - } - - const wallet = this.walletManager.getWallet(walletId); - if (!wallet) { - throw new WalletKitError(ERROR_CODES.WALLET_NOT_FOUND, `Wallet not found: ${walletId}`); - } - - return wallet.getClient(); - } } /** diff --git a/packages/walletkit/src/core/TonWalletKit.ts b/packages/walletkit/src/core/TonWalletKit.ts index dad6a2fa4..471d252af 100644 --- a/packages/walletkit/src/core/TonWalletKit.ts +++ b/packages/walletkit/src/core/TonWalletKit.ts @@ -61,13 +61,17 @@ import type { RequestErrorEvent, DisconnectionEvent, SignDataRequestEvent, + SignMessageRequestEvent, ConnectionRequestEvent, SendTransactionApprovalResponse, SignDataApprovalResponse, + SignMessageApprovalResponse, TONConnectSession, ConnectionApprovalResponse, + EmbeddedRequestEvent, } from '../api/models'; import { asAddressFriendly } from '../utils'; +import { parseEmbeddedRequestFromReqParam } from '../utils/embeddedRequest'; import type { ProviderFactoryContext } from '../types/factory'; const log = globalLogger.createChild('TonWalletKit'); @@ -98,7 +102,6 @@ export class TonWalletKit implements ITonWalletKit { private initializer: Initializer; private eventProcessor!: StorageEventProcessor; private bridgeManager!: BridgeManager; - private config: TonWalletKitOptions; // Event emitter for this kit instance @@ -522,6 +525,20 @@ export class TonWalletKit implements ITonWalletKit { this.eventRouter.removeSignDataRequestCallback(); } + onSignMessageRequest(cb: (event: SignMessageRequestEvent) => void): void { + if (this.eventRouter) { + this.eventRouter.onSignMessageRequest(cb); + } else { + this.ensureInitialized().then(() => { + this.eventRouter.onSignMessageRequest(cb); + }); + } + } + + removeSignMessageRequestCallback(): void { + this.eventRouter.removeSignMessageRequestCallback(); + } + removeDisconnectCallback(): void { this.eventRouter.removeDisconnectCallback(); } @@ -670,6 +687,7 @@ export class TonWalletKit implements ITonWalletKit { clientId: string; requestId: string; r: string; + e?: string; returnStrategy?: string; }): RawBridgeEventConnect | undefined { const rString = params.r; @@ -678,7 +696,8 @@ export class TonWalletKit implements ITonWalletKit { if (!r?.manifestUrl || !params.clientId) { return undefined; } - return { + + const bridgeEvent: RawBridgeEventConnect = { from: params.clientId, id: params.requestId, method: 'connect', @@ -692,11 +711,34 @@ export class TonWalletKit implements ITonWalletKit { timestamp: Date.now(), domain: '', }; + + // Parse embedded embedded request if present + if (params.e) { + // check if we have embedded requests supported in features + const hasEmbeddedRequests = this.config.deviceInfo?.features.some( + (feature) => typeof feature === 'object' && feature.name === 'EmbeddedRequest', + ); + if (hasEmbeddedRequests) { + bridgeEvent.embeddedRequest = parseEmbeddedRequestFromReqParam(params.e); + } else { + log.warn( + 'Embedded request feature is not supported in features, but we received request with embedded request payload', + { + features: this.config.deviceInfo?.features, + }, + ); + } + } + + return bridgeEvent; } // === Request Processing API (Delegated) === - async approveConnectRequest(event: ConnectionRequestEvent, response?: ConnectionApprovalResponse): Promise { + async approveConnectRequest( + event: ConnectionRequestEvent, + response?: ConnectionApprovalResponse, + ): Promise { await this.ensureInitialized(); return this.requestProcessor.approveConnectRequest(event, response); } @@ -739,6 +781,19 @@ export class TonWalletKit implements ITonWalletKit { return this.requestProcessor.rejectSignDataRequest(event, reason); } + async approveSignMessageRequest( + event: SignMessageRequestEvent, + response?: SignMessageApprovalResponse, + ): Promise { + await this.ensureInitialized(); + return this.requestProcessor.approveSignMessageRequest(event, response); + } + + async rejectSignMessageRequest(event: SignMessageRequestEvent, reason?: string): Promise { + await this.ensureInitialized(); + return this.requestProcessor.rejectSignMessageRequest(event, reason); + } + // === TON Client Access === /** diff --git a/packages/walletkit/src/core/wallet/extensions/ton.ts b/packages/walletkit/src/core/wallet/extensions/ton.ts index 7532aea77..a82891ac9 100644 --- a/packages/walletkit/src/core/wallet/extensions/ton.ts +++ b/packages/walletkit/src/core/wallet/extensions/ton.ts @@ -10,6 +10,7 @@ import { isValidAddress } from '../../../utils/address'; import { isValidNanotonAmount, validateTransactionMessage } from '../../../validation'; import { CallForSuccess } from '../../../utils/retry'; import { createTransactionPreview as createTransactionPreviewHelper } from '../../../utils/toncenterEmulation'; +import type { TransactionPreviewOptions } from '../../../utils/toncenterEmulation'; import { createCommentPayloadBase64 } from '../../../utils/messageBuilders'; import { getNormalizedExtMessageHash } from '../../../utils/getNormalizedExtMessageHash'; import { ERROR_CODES, WalletKitError } from '../../../errors'; @@ -99,9 +100,12 @@ export class WalletTonClass implements WalletTonInterface { async getTransactionPreview( this: Wallet, param: TransactionRequest | Promise, + options?: TransactionPreviewOptions, ): Promise { const transaction = await param; - const preview = await CallForSuccess(() => createTransactionPreviewHelper(this.client, transaction, this)); + const preview = await CallForSuccess(() => + createTransactionPreviewHelper(this.client, transaction, this, options), + ); return preview; } diff --git a/packages/walletkit/src/handlers/ConnectHandler.ts b/packages/walletkit/src/handlers/ConnectHandler.ts index 74a8a9c52..635e539d4 100644 --- a/packages/walletkit/src/handlers/ConnectHandler.ts +++ b/packages/walletkit/src/handlers/ConnectHandler.ts @@ -72,6 +72,7 @@ export class ConnectHandler isJsBridge: event.isJsBridge, tabId: event.tabId, returnStrategy: event.params.returnStrategy, + embeddedRequest: event.embeddedRequest, }; // Send wallet-connect-request-received event diff --git a/packages/walletkit/src/handlers/SignDataHandler.ts b/packages/walletkit/src/handlers/SignDataHandler.ts index 14faa761f..5c6d4eed5 100644 --- a/packages/walletkit/src/handlers/SignDataHandler.ts +++ b/packages/walletkit/src/handlers/SignDataHandler.ts @@ -8,16 +8,13 @@ // Sign data request handler -import type { SignDataPayload as TonConnectSignDataPayload } from '@tonconnect/protocol'; - import type { RawBridgeEvent, EventHandler, RawBridgeEventSignData } from '../types/internal'; +import { parseConnectSignDataParamContent } from '../types/internal'; import { BasicHandler } from './BasicHandler'; import { globalLogger } from '../core/Logger'; -import { validateSignDataPayload } from '../validation/signData'; import { WalletKitError, ERROR_CODES } from '../errors'; import type { WalletManager } from '../core/WalletManager'; -import type { SignDataPayload, SignData, SignDataRequestEvent, SignDataPreview, Base64String } from '../api/models'; -import { Network } from '../api/models'; +import type { SignDataPayload, SignData, SignDataRequestEvent, SignDataPreview } from '../api/models'; import type { Analytics, AnalyticsManager } from '../analytics'; import type { TONConnectSessionManager } from '../api/interfaces/TONConnectSessionManager'; @@ -113,57 +110,7 @@ export class SignDataHandler * Parse data to sign from bridge event */ private parseDataToSign(event: RawBridgeEventSignData): SignDataPayload | undefined { - try { - const parsed = JSON.parse(event.params[0]) as TonConnectSignDataPayload; - - const validationResult = validateSignDataPayload(parsed); - - if (validationResult) { - log.error('Invalid data to sign found in request', { validationResult }); - return undefined; - } - - if (parsed === undefined) { - return undefined; - } - - let signData: SignData; - - if (parsed.type === 'text') { - signData = { - type: 'text', - value: { - content: parsed.text, - }, - }; - } else if (parsed.type === 'binary') { - signData = { - type: 'binary', - value: { - content: parsed.bytes as Base64String, - }, - }; - } else if (parsed.type === 'cell') { - signData = { - type: 'cell', - value: { - schema: parsed.schema, - content: parsed.cell as Base64String, - }, - }; - } else { - return undefined; - } - - return { - network: parsed.network ? Network.custom(parsed.network) : undefined, - fromAddress: parsed.from, - data: signData, - }; - } catch (error) { - log.error('Invalid data to sign found in request', { error }); - return undefined; - } + return parseConnectSignDataParamContent(event); } /** diff --git a/packages/walletkit/src/handlers/SignMessageHandler.ts b/packages/walletkit/src/handlers/SignMessageHandler.ts new file mode 100644 index 000000000..4c47673eb --- /dev/null +++ b/packages/walletkit/src/handlers/SignMessageHandler.ts @@ -0,0 +1,130 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { WalletResponseTemplateError } from '@tonconnect/protocol'; +import { SIGN_MESSAGE_ERROR_CODES } from '@tonconnect/protocol'; + +import type { TonWalletKitOptions, ValidationResult } from '../types'; +import type { + RawBridgeEvent, + EventHandler, + RawBridgeEventSignMessage, + RawBridgeEventTransaction, +} from '../types/internal'; +import { globalLogger } from '../core/Logger'; +import { BasicHandler } from './BasicHandler'; +import type { EventEmitter } from '../core/EventEmitter'; +import type { WalletKitEvents } from '../types/emitter'; +import type { WalletManager } from '../core/WalletManager'; +import type { Wallet } from '../api/interfaces'; +import type { TransactionRequest, SignMessageRequestEvent } from '../api/models'; +import type { Analytics, AnalyticsManager } from '../analytics'; +import type { TONConnectSessionManager } from '../api/interfaces/TONConnectSessionManager'; +import { createTransactionPreviewIfPossible } from '../utils'; +import { checkTransactionRequestItems, getWalletFromEvent, parseTonConnectTransactionRequest } from '../utils/events'; + +const log = globalLogger.createChild('SignMessageHandler'); + +// Error response shape (mirrors SendTransactionRpcResponseError but for signMessage) +interface SignMessageRpcResponseError { + error: { code: number; message: string }; + id: string; +} + +export class SignMessageHandler + extends BasicHandler + implements EventHandler +{ + private eventEmitter: EventEmitter; + private analytics?: Analytics; + + constructor( + notify: (event: SignMessageRequestEvent) => void, + private readonly config: TonWalletKitOptions, + eventEmitter: EventEmitter, + private readonly walletManager: WalletManager, + private readonly sessionManager: TONConnectSessionManager, + analyticsManager?: AnalyticsManager, + ) { + super(notify); + this.eventEmitter = eventEmitter; + this.sessionManager = sessionManager; + this.analytics = analyticsManager?.scoped(); + } + + canHandle(event: RawBridgeEvent): event is RawBridgeEventSignMessage { + return event.method === 'signMessage'; + } + + async handle(event: RawBridgeEventSignMessage): Promise { + const wallet = getWalletFromEvent(this.walletManager, event); + if (!wallet) { + log.error('Wallet not found', { event }); + return { + error: { + code: SIGN_MESSAGE_ERROR_CODES.UNKNOWN_APP_ERROR, + message: 'Wallet not found', + }, + id: event.id, + } as SignMessageRpcResponseError; + } + + const requestValidation = this.parseTonConnectTransactionRequest(event, wallet); + if (!requestValidation.result || !requestValidation?.validation?.isValid) { + log.error('Failed to parse sign message request', { event, requestValidation }); + this.eventEmitter.emit('eventError', event, 'sign-message-handler'); + return { + error: { + code: SIGN_MESSAGE_ERROR_CODES.BAD_REQUEST_ERROR, + message: 'Failed to parse sign message request', + }, + id: event.id, + } as SignMessageRpcResponseError; + } + + const request = await checkTransactionRequestItems(requestValidation.result, wallet); + const preview = await createTransactionPreviewIfPossible(this.config, wallet.client, request, wallet, { + mode: 'sign', + }); + + const signMessageEvent: SignMessageRequestEvent = { + ...event, + request, + preview: { + data: preview, + }, + dAppInfo: event.dAppInfo ?? {}, + walletId: wallet.getWalletId(), + walletAddress: wallet.getAddress(), + }; + + if (this.analytics) { + const sessionData = event.from ? await this.sessionManager.getSession(event.from) : undefined; + this.analytics?.emitWalletTransactionRequestReceived({ + trace_id: event.traceId, + client_id: event.from, + wallet_id: sessionData?.publicKey, + dapp_name: event.dAppInfo?.name, + network_id: wallet.getNetwork().chainId, + origin_url: event.dAppInfo?.url, + }); + } + + return signMessageEvent; + } + + private parseTonConnectTransactionRequest( + event: RawBridgeEventTransaction | RawBridgeEventSignMessage, + wallet: Wallet, + ): { + result: TransactionRequest | undefined; + validation: ValidationResult; + } { + return parseTonConnectTransactionRequest(event, wallet); + } +} diff --git a/packages/walletkit/src/handlers/TransactionHandler.ts b/packages/walletkit/src/handlers/TransactionHandler.ts index fcba5bf05..101497234 100644 --- a/packages/walletkit/src/handlers/TransactionHandler.ts +++ b/packages/walletkit/src/handlers/TransactionHandler.ts @@ -6,33 +6,26 @@ * */ -import { Address } from '@ton/core'; -import type { ChainId, SendTransactionRpcResponseError, WalletResponseTemplateError } from '@tonconnect/protocol'; +import type { SendTransactionRpcResponseError, WalletResponseTemplateError } from '@tonconnect/protocol'; import { SEND_TRANSACTION_ERROR_CODES } from '@tonconnect/protocol'; import type { TonWalletKitOptions, ValidationResult } from '../types'; -import { toTransactionRequest, parseConnectTransactionParamContent } from '../types/internal'; import type { RawBridgeEvent, EventHandler, RawBridgeEventTransaction, - RawConnectTransactionParamContent, + RawBridgeEventSignMessage, } from '../types/internal'; -import { validateTransactionMessages as validateTonConnectTransactionMessages } from '../validation/transaction'; import { globalLogger } from '../core/Logger'; -import { isValidAddress } from '../utils/address'; -import { createTransactionPreview as createTransactionPreviewHelper } from '../utils/toncenterEmulation'; +import { createTransactionPreviewIfPossible } from '../utils/toncenterEmulation'; import { BasicHandler } from './BasicHandler'; -import { CallForSuccess } from '../utils/retry'; import type { WalletKitEventEmitter } from '../types/emitter'; import type { WalletManager } from '../core/WalletManager'; -import type { ReturnWithValidationResult } from '../validation/types'; -import { WalletKitError, ERROR_CODES } from '../errors'; import type { Wallet } from '../api/interfaces'; -import type { TransactionEmulatedPreview, TransactionRequest, SendTransactionRequestEvent } from '../api/models'; -import { Result } from '../api/models'; +import type { TransactionRequest, SendTransactionRequestEvent } from '../api/models'; import type { Analytics, AnalyticsManager } from '../analytics'; import type { TONConnectSessionManager } from '../api/interfaces/TONConnectSessionManager'; +import { checkTransactionRequestItems, getWalletFromEvent, parseTonConnectTransactionRequest } from '../utils/events'; const log = globalLogger.createChild('TransactionHandler'); @@ -61,25 +54,9 @@ export class TransactionHandler } async handle(event: RawBridgeEventTransaction): Promise { - // Support both walletId (new) and walletAddress (legacy) - const walletId = event.walletId; - const walletAddress = event.walletAddress; - - if (!walletId && !walletAddress) { - log.error('Wallet ID not found', { event }); - return { - error: { - code: SEND_TRANSACTION_ERROR_CODES.UNKNOWN_APP_ERROR, - message: 'Wallet ID not found', - }, - id: event.id, - } as SendTransactionRpcResponseError; - } - - // Try to get wallet by walletId first, fall back to address search - const wallet = walletId ? this.walletManager.getWallet(walletId) : undefined; + const wallet = getWalletFromEvent(this.walletManager, event); if (!wallet) { - log.error('Wallet not found', { event, walletId, walletAddress }); + log.error('Wallet not found', { event }); return { error: { code: SEND_TRANSACTION_ERROR_CODES.UNKNOWN_APP_ERROR, @@ -102,31 +79,9 @@ export class TransactionHandler id: event.id, } as SendTransactionRpcResponseError; } - const request = requestValidation.result; - let preview: TransactionEmulatedPreview | undefined; - if (!this.config.eventProcessor?.disableTransactionEmulation) { - try { - preview = await CallForSuccess(() => createTransactionPreviewHelper(wallet.client, request, wallet)); - // Emit emulation result event for jetton caching and other components - if (preview.result === Result.success && preview.trace) { - try { - this.eventEmitter.emit('emulationResult', preview.trace, 'transaction-handler'); - } catch (error) { - log.warn('Error emitting emulation result event', { error }); - } - } - } catch (error) { - log.error('Failed to create transaction preview', { error }); - preview = { - error: { - code: ERROR_CODES.UNKNOWN_EMULATION_ERROR, - message: 'Unknown emulation error', - }, - result: Result.failure, - }; - } - } + const request = await checkTransactionRequestItems(requestValidation.result, wallet); + const preview = await createTransactionPreviewIfPossible(this.config, wallet.client, request, wallet); const txEvent: SendTransactionRequestEvent = { ...event, @@ -135,8 +90,8 @@ export class TransactionHandler data: preview, }, dAppInfo: event.dAppInfo ?? {}, - walletId: walletId ?? this.walletManager.getWalletId(wallet), - walletAddress: walletAddress ?? wallet.getAddress(), + walletId: wallet.getWalletId(), + walletAddress: wallet.getAddress(), }; if (this.analytics) { @@ -162,129 +117,12 @@ export class TransactionHandler */ private parseTonConnectTransactionRequest( - event: RawBridgeEventTransaction, + event: RawBridgeEventTransaction | RawBridgeEventSignMessage, wallet: Wallet, ): { result: TransactionRequest | undefined; validation: ValidationResult; } { - let errors: string[] = []; - try { - if (event.params.length !== 1) { - throw new WalletKitError( - ERROR_CODES.INVALID_REQUEST_EVENT, - 'Invalid transaction request - expected exactly 1 parameter', - undefined, - { paramCount: event.params.length, eventId: event.id }, - ); - } - const rawParams = JSON.parse(event.params[0]) as RawConnectTransactionParamContent; - const params = parseConnectTransactionParamContent(rawParams); - - const validUntilValidation = this.validateValidUntil(params.validUntil); - if (!validUntilValidation.isValid) { - errors = errors.concat(validUntilValidation.errors); - } else { - params.validUntil = validUntilValidation.result; - } - - const networkValidation = this.validateNetwork(params.network, wallet); - if (!networkValidation.isValid) { - errors = errors.concat(networkValidation.errors); - } else { - params.network = networkValidation.result; - } - - const fromValidation = this.validateFrom(params.from, wallet); - if (!fromValidation.isValid) { - errors = errors.concat(fromValidation.errors); - } else { - params.from = fromValidation.result; - } - - const isTonConnect = !event.isLocal; - const messagesValidation = validateTonConnectTransactionMessages(params.messages, isTonConnect); - if (!messagesValidation.isValid) { - errors = errors.concat(messagesValidation.errors); - } - - return { - result: toTransactionRequest(params), - validation: { isValid: errors.length === 0, errors: errors }, - }; - } catch (error) { - log.error('Failed to parse transaction request', { error }); - errors.push('Failed to parse transaction request'); - return { - result: undefined, - validation: { isValid: errors.length === 0, errors: errors }, - }; - } - } - - /** - * Parse network from various possible formats - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private validateNetwork(network: any, wallet: Wallet): ReturnWithValidationResult { - let errors: string[] = []; - if (typeof network === 'string') { - const walletNetwork = wallet.getNetwork(); - if (network !== walletNetwork.chainId) { - errors.push('Invalid network not equal to wallet network'); - } else { - return { result: network, isValid: errors.length === 0, errors: errors }; - } - } else { - errors.push('Invalid network not a string'); - } - - return { result: undefined, isValid: errors.length === 0, errors: errors }; - } - - private validateFrom(from: unknown, wallet: Wallet): ReturnWithValidationResult { - let errors: string[] = []; - - if (typeof from !== 'string') { - errors.push('Invalid from address not a string'); - return { result: '', isValid: errors.length === 0, errors: errors }; - } - - if (!isValidAddress(from)) { - errors.push('Invalid from address'); - return { result: '', isValid: errors.length === 0, errors: errors }; - } - - const fromAddress = Address.parse(from); - const walletAddress = Address.parse(wallet.getAddress()); - if (!fromAddress.equals(walletAddress)) { - errors.push('Invalid from address not equal to wallet address'); - return { result: '', isValid: errors.length === 0, errors: errors }; - } - - return { result: from, isValid: errors.length === 0, errors: errors }; - } - - /** - * Parse validUntil timestamp - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private validateValidUntil(validUntil: any): ReturnWithValidationResult { - let errors: string[] = []; - if (typeof validUntil === 'undefined') { - return { result: 0, isValid: errors.length === 0, errors: errors }; - } - if (typeof validUntil !== 'number' || isNaN(validUntil)) { - errors.push('Invalid validUntil timestamp not a number'); - return { result: 0, isValid: errors.length === 0, errors: errors }; - } - - const now = Math.floor(Date.now() / 1000); - if (validUntil < now) { - errors.push('Invalid validUntil timestamp'); - return { result: 0, isValid: errors.length === 0, errors: errors }; - } - - return { result: validUntil, isValid: errors.length === 0, errors: errors }; + return parseTonConnectTransactionRequest(event, wallet); } } diff --git a/packages/walletkit/src/handlers/transactionValidators.ts b/packages/walletkit/src/handlers/transactionValidators.ts new file mode 100644 index 000000000..cf847ec30 --- /dev/null +++ b/packages/walletkit/src/handlers/transactionValidators.ts @@ -0,0 +1,67 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { Address } from '@ton/core'; +import type { ChainId } from '@tonconnect/protocol'; + +import type { ReturnWithValidationResult } from '../validation/types'; +import { isValidAddress } from '../utils/address'; +import type { Wallet } from '../api/interfaces'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function validateNetwork(network: any, wallet: Wallet): ReturnWithValidationResult { + let errors: string[] = []; + if (typeof network === 'string') { + const walletNetwork = wallet.getNetwork(); + if (network !== walletNetwork.chainId) { + errors.push('Invalid network not equal to wallet network'); + } else { + return { result: network, isValid: errors.length === 0, errors: errors }; + } + } else { + errors.push('Invalid network not a string'); + } + return { result: undefined, isValid: errors.length === 0, errors: errors }; +} + +export function validateFrom(from: unknown, wallet: Wallet): ReturnWithValidationResult { + let errors: string[] = []; + if (typeof from !== 'string') { + errors.push('Invalid from address not a string'); + return { result: '', isValid: errors.length === 0, errors: errors }; + } + if (!isValidAddress(from)) { + errors.push('Invalid from address'); + return { result: '', isValid: errors.length === 0, errors: errors }; + } + const fromAddress = Address.parse(from); + const walletAddress = Address.parse(wallet.getAddress()); + if (!fromAddress.equals(walletAddress)) { + errors.push('Invalid from address not equal to wallet address'); + return { result: '', isValid: errors.length === 0, errors: errors }; + } + return { result: from, isValid: errors.length === 0, errors: errors }; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function validateValidUntil(validUntil: any): ReturnWithValidationResult { + let errors: string[] = []; + if (typeof validUntil === 'undefined') { + return { result: 0, isValid: errors.length === 0, errors: errors }; + } + if (typeof validUntil !== 'number' || isNaN(validUntil)) { + errors.push('Invalid validUntil timestamp not a number'); + return { result: 0, isValid: errors.length === 0, errors: errors }; + } + const now = Math.floor(Date.now() / 1000); + if (validUntil < now) { + errors.push('Invalid validUntil timestamp'); + return { result: 0, isValid: errors.length === 0, errors: errors }; + } + return { result: validUntil, isValid: errors.length === 0, errors: errors }; +} diff --git a/packages/walletkit/src/types/emitter.ts b/packages/walletkit/src/types/emitter.ts index 735b3d032..26ba0bb19 100644 --- a/packages/walletkit/src/types/emitter.ts +++ b/packages/walletkit/src/types/emitter.ts @@ -7,7 +7,7 @@ */ import type { TransactionEmulatedTrace } from '../api/models'; -import type { RawBridgeEventRestoreConnection, RawBridgeEventTransaction } from './internal'; +import type { RawBridgeEvent, RawBridgeEventRestoreConnection } from './internal'; import type { EventEmitter } from '../core/EventEmitter'; import type { StreamingEvents } from '../api/models'; @@ -21,7 +21,7 @@ export type SharedKitEvents = StreamingEvents; */ export type WalletKitEvents = { restoreConnection: RawBridgeEventRestoreConnection; - eventError: RawBridgeEventTransaction; + eventError: RawBridgeEvent; emulationResult: TransactionEmulatedTrace; bridgeStorageUpdated: object; } & SharedKitEvents; diff --git a/packages/walletkit/src/types/internal.ts b/packages/walletkit/src/types/internal.ts index 4ac6dcf99..7012f2f39 100644 --- a/packages/walletkit/src/types/internal.ts +++ b/packages/walletkit/src/types/internal.ts @@ -12,6 +12,7 @@ import type { ConnectItem, SendTransactionRpcRequest, SignDataRpcRequest, + SignDataPayload as TonConnectSignDataPayload, WalletResponseTemplateError, ChainId, } from '@tonconnect/protocol'; @@ -24,10 +25,17 @@ import type { TransactionRequestMessage, BridgeEvent, Base64String, + SignData, + SignDataPayload, } from '../api/models'; +import { Network } from '../api/models'; +import { validateSignDataPayload } from '../validation/signData'; +import type { StructuredItem } from '../api/models/transactions/StructuredItem'; import { SendModeFromValue } from '../utils/sendMode'; import { SendModeToValue } from '../utils/sendMode'; import { asAddressFriendly } from '../utils/address'; +import { asBase64 } from '../utils/base64'; +import type { EmbeddedRequest } from '../api/models/bridge/EmbeddedRequest'; // import type { WalletInterface } from './wallet'; @@ -79,6 +87,8 @@ export interface RawBridgeEventConnect extends BridgeEvent { returnStrategy?: string; }; timestamp?: number; + /** Parsed embedded request from the `req` URL parameter (embedded requests). */ + embeddedRequest?: EmbeddedRequest; } export interface RawBridgeEventRestoreConnection extends BridgeEvent { @@ -101,6 +111,119 @@ export interface ConnectTransactionParamMessage { mode?: number; } +/** Snake_case wire-format items as received in JSON-RPC payload */ +export type RawStructuredItem = RawTonTransferItem | RawJettonTransferItem | RawNftTransferItem; + +export interface RawTonTransferItem { + type: 'ton'; + address: string; + amount: string; + payload?: string; + stateInit?: string; + extra_currency?: { [k: number]: string }; +} + +export interface RawJettonTransferItem { + type: 'jetton'; + master: string; + destination: string; + amount: string; + attachAmount?: string; + queryId?: string; + responseDestination?: string; + customPayload?: string; + forwardAmount?: string; + forwardPayload?: string; +} + +export interface RawNftTransferItem { + type: 'nft'; + nftAddress: string; + newOwner: string; + attachAmount?: string; + queryId?: string; + responseDestination?: string; + customPayload?: string; + forwardAmount?: string; + forwardPayload?: string; +} + +export function parseRawStructuredItem(raw: RawStructuredItem): StructuredItem { + switch (raw.type) { + case 'ton': + return { + type: 'ton', + address: raw.address, + amount: raw.amount, + payload: raw.payload ? asBase64(raw.payload) : undefined, + stateInit: raw.stateInit ? asBase64(raw.stateInit) : undefined, + extraCurrency: raw.extra_currency as ExtraCurrencies | undefined, + }; + case 'jetton': + return { + type: 'jetton', + master: raw.master, + destination: raw.destination, + amount: raw.amount, + attachAmount: raw.attachAmount, + queryId: raw.queryId, + responseDestination: raw.responseDestination, + customPayload: raw.customPayload ? asBase64(raw.customPayload) : undefined, + forwardAmount: raw.forwardAmount, + forwardPayload: raw.forwardPayload ? asBase64(raw.forwardPayload) : undefined, + }; + case 'nft': + return { + type: 'nft', + nftAddress: raw.nftAddress, + newOwner: raw.newOwner, + attachAmount: raw.attachAmount, + queryId: raw.queryId, + responseDestination: raw.responseDestination, + customPayload: raw.customPayload ? asBase64(raw.customPayload) : undefined, + forwardAmount: raw.forwardAmount, + forwardPayload: raw.forwardPayload ? asBase64(raw.forwardPayload) : undefined, + }; + } +} + +export function toRawStructuredItem(item: StructuredItem): RawStructuredItem { + switch (item.type) { + case 'ton': + return { + type: 'ton', + address: item.address, + amount: item.amount, + payload: item.payload, + stateInit: item.stateInit, + extra_currency: item.extraCurrency, + }; + case 'jetton': + return { + type: 'jetton', + master: item.master, + destination: item.destination, + amount: item.amount, + attachAmount: item.attachAmount, + responseDestination: item.responseDestination, + customPayload: item.customPayload, + forwardAmount: item.forwardAmount, + forwardPayload: item.forwardPayload, + }; + case 'nft': + return { + type: 'nft', + nftAddress: item.nftAddress, + newOwner: item.newOwner, + attachAmount: item.attachAmount, + responseDestination: item.responseDestination, + customPayload: item.customPayload, + forwardAmount: item.forwardAmount, + forwardPayload: item.forwardPayload, + }; + } +} + export function toExtraCurrencies(extraCurrency: ConnectExtraCurrency | undefined): ExtraCurrencies | undefined { if (!extraCurrency) { return undefined; @@ -109,17 +232,20 @@ export function toExtraCurrencies(extraCurrency: ConnectExtraCurrency | undefine } /** - * Raw transaction params as received from TON Connect protocol + * Raw transaction params as received from TON Connect protocol. + * Contains either `messages` (raw format) or `items` (structured format), never both. */ export interface RawConnectTransactionParamContent { - messages: ConnectTransactionParamMessage[]; + messages?: ConnectTransactionParamMessage[]; + items?: RawStructuredItem[]; network?: ChainId; valid_until?: number; from?: string; } export interface ConnectTransactionParamContent { - messages: ConnectTransactionParamMessage[]; + messages?: ConnectTransactionParamMessage[]; + items?: StructuredItem[]; network?: ChainId; validUntil?: number; from?: string; @@ -133,12 +259,51 @@ export function parseConnectTransactionParamContent( ): ConnectTransactionParamContent { return { messages: raw.messages, + items: raw.items?.map(parseRawStructuredItem), network: raw.network, validUntil: raw.valid_until, from: raw.from, }; } +/** + * Parse raw TON Connect sign data params to internal SignDataPayload format + */ +export function parseConnectSignDataParamContent(event: RawBridgeEventSignData): SignDataPayload | undefined { + try { + const parsed = JSON.parse(event.params[0]) as TonConnectSignDataPayload; + + const validationResult = validateSignDataPayload(parsed); + if (validationResult) { + return undefined; + } + + if (parsed === undefined) { + return undefined; + } + + let signData: SignData; + + if (parsed.type === 'text') { + signData = { type: 'text', value: { content: parsed.text } }; + } else if (parsed.type === 'binary') { + signData = { type: 'binary', value: { content: parsed.bytes as Base64String } }; + } else if (parsed.type === 'cell') { + signData = { type: 'cell', value: { schema: parsed.schema, content: parsed.cell as Base64String } }; + } else { + return undefined; + } + + return { + network: parsed.network ? Network.custom(parsed.network) : undefined, + fromAddress: parsed.from, + data: signData, + }; + } catch { + return undefined; + } +} + export function toTransactionRequestMessage(msg: ConnectTransactionParamMessage): TransactionRequestMessage { // Check that msg.address is valid address asAddressFriendly(msg.address); @@ -169,7 +334,8 @@ export function toConnectTransactionParamMessage(message: TransactionRequestMess */ export function toTransactionRequest(params: ConnectTransactionParamContent): TransactionRequest { return { - messages: params.messages.map(toTransactionRequestMessage), + messages: params.messages?.map(toTransactionRequestMessage) ?? [], + items: params.items, network: params.network ? { chainId: params.network } : undefined, validUntil: params.validUntil, fromAddress: params.from, @@ -182,6 +348,7 @@ export function toTransactionRequest(params: ConnectTransactionParamContent): Tr export function toConnectTransactionParamContent(request: TransactionRequest): RawConnectTransactionParamContent { return { messages: request.messages.map(toConnectTransactionParamMessage), + items: request.items?.map(toRawStructuredItem), network: request.network?.chainId, valid_until: request.validUntil, from: request.fromAddress, @@ -191,6 +358,15 @@ export function toConnectTransactionParamContent(request: TransactionRequest): R export type RawBridgeEventTransaction = BridgeEvent & SendTransactionRpcRequest; export type RawBridgeEventSignData = BridgeEvent & SignDataRpcRequest; +// TODO: Replace with BridgeEvent & SignMessageRpcRequest from @tonconnect/protocol once +// signMessage is standardized and added to the protocol package (currently absent in v2.4.0). +export interface RawBridgeEventSignMessage extends BridgeEvent { + id: string; + method: 'signMessage'; + params: [string]; // JSON-stringified, same format as sendTransaction params + timestamp?: number; +} + export interface RawBridgeEventDisconnect extends BridgeEvent { id: string; method: 'disconnect'; @@ -206,10 +382,11 @@ export type RawBridgeEvent = | RawBridgeEventRestoreConnection | RawBridgeEventTransaction | RawBridgeEventSignData + | RawBridgeEventSignMessage | RawBridgeEventDisconnect; // Internal event routing types -export type EventType = 'connect' | 'sendTransaction' | 'signData' | 'disconnect' | 'restoreConnection'; +export type EventType = 'connect' | 'sendTransaction' | 'signData' | 'signMessage' | 'disconnect' | 'restoreConnection'; export interface EventHandler { canHandle(event: RawBridgeEvent): event is V; diff --git a/packages/walletkit/src/types/kit.ts b/packages/walletkit/src/types/kit.ts index 878fe718f..be4bc7a03 100644 --- a/packages/walletkit/src/types/kit.ts +++ b/packages/walletkit/src/types/kit.ts @@ -22,11 +22,14 @@ import type { RequestErrorEvent, DisconnectionEvent, SignDataRequestEvent, + SignMessageRequestEvent, ConnectionRequestEvent, SignDataApprovalResponse, + SignMessageApprovalResponse, TONConnectSession, SendTransactionApprovalResponse, ConnectionApprovalResponse, + EmbeddedRequestEvent, } from '../api/models'; import type { SwapAPI, StakingAPI } from '../api/interfaces'; import type { NetworkManager } from '../core/NetworkManager'; @@ -101,8 +104,11 @@ export interface ITonWalletKit { // === Request Processing === - /** Approve a connect request */ - approveConnectRequest(event: ConnectionRequestEvent, response?: ConnectionApprovalResponse): Promise; + /** Approve a connect request. Returns an EmbeddedRequestEvent if the event carries an embedded request. */ + approveConnectRequest( + event: ConnectionRequestEvent, + response?: ConnectionApprovalResponse, + ): Promise; /** Reject a connect request */ rejectConnectRequest( event: ConnectionRequestEvent, @@ -131,6 +137,15 @@ export interface ITonWalletKit { /** Reject a sign data request */ rejectSignDataRequest(event: SignDataRequestEvent, reason?: string): Promise; + /** Approve a sign message (sign-only transaction) request */ + approveSignMessageRequest( + event: SignMessageRequestEvent, + response?: SignMessageApprovalResponse, + ): Promise; + + /** Reject a sign message request */ + rejectSignMessageRequest(event: SignMessageRequestEvent, reason?: string): Promise; + // === Event Handlers === /** Register connect request handler */ @@ -142,6 +157,9 @@ export interface ITonWalletKit { /** Register sign data request handler */ onSignDataRequest(cb: (event: SignDataRequestEvent) => void): void; + /** Register sign message request handler */ + onSignMessageRequest(cb: (event: SignMessageRequestEvent) => void): void; + /** Register disconnect handler */ onDisconnect(cb: (event: DisconnectionEvent) => void): void; @@ -152,9 +170,12 @@ export interface ITonWalletKit { removeConnectRequestCallback(cb: (event: ConnectionRequestEvent) => void): void; removeTransactionRequestCallback(cb: (event: SendTransactionRequestEvent) => void): void; removeSignDataRequestCallback(cb: (event: SignDataRequestEvent) => void): void; + removeSignMessageRequestCallback(cb: (event: SignMessageRequestEvent) => void): void; removeDisconnectCallback(cb: (event: DisconnectionEvent) => void): void; removeErrorCallback(cb: (event: RequestErrorEvent) => void): void; + // === Jettons API === + /** Jettons API access */ jettons: JettonsAPI; diff --git a/packages/walletkit/src/utils/embeddedRequest.ts b/packages/walletkit/src/utils/embeddedRequest.ts new file mode 100644 index 000000000..e15d3f24f --- /dev/null +++ b/packages/walletkit/src/utils/embeddedRequest.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { AppRequest } from '@tonconnect/protocol'; +import { decodeEmbeddedRequestParam } from '@tonconnect/protocol'; + +import type { EmbeddedRequest } from '../api/models'; +import type { RawConnectTransactionParamContent, RawBridgeEventSignData } from '../types/internal'; +import { + parseConnectTransactionParamContent, + toTransactionRequest, + parseConnectSignDataParamContent, +} from '../types/internal'; +import { globalLogger } from '../core/Logger'; + +const log = globalLogger.createChild('embeddedRequestParser'); + +/** + * Parse the `req` URL parameter into an EmbeddedRequest using the protocol's parseEmbeddedRequest. + * Returns undefined if the parameter is malformed or contains an unrecognized method. + */ +export function parseEmbeddedRequestFromReqParam(reqParam: string): EmbeddedRequest | undefined { + try { + const parsed = decodeEmbeddedRequestParam(reqParam); + return toEmbeddedRequest(parsed); + } catch (error) { + log.warn('Failed to parse embedded request req parameter', { error }); + return undefined; + } +} + +function toEmbeddedRequest( + parsed: Omit, 'id'>, +): EmbeddedRequest | undefined { + switch (parsed.method) { + case 'sendTransaction': + case 'signMessage': { + const raw = JSON.parse(parsed.params[0]) as RawConnectTransactionParamContent; + const content = parseConnectTransactionParamContent(raw); + const transactionRequest = toTransactionRequest(content); + return { method: parsed.method, transactionRequest }; + } + case 'signData': { + const signDataEvent = { ...parsed, id: '' } as RawBridgeEventSignData; + const payload = parseConnectSignDataParamContent(signDataEvent); + if (!payload) return undefined; + return { method: 'signData', payload }; + } + default: + log.warn('Unknown embedded request method', { method: (parsed as { method: string }).method }); + return undefined; + } +} diff --git a/packages/walletkit/src/utils/events.ts b/packages/walletkit/src/utils/events.ts new file mode 100644 index 000000000..e6a844d07 --- /dev/null +++ b/packages/walletkit/src/utils/events.ts @@ -0,0 +1,152 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { Wallet } from '../api/interfaces'; +import type { TransactionRequest } from '../api/models'; +import type { WalletManager } from '../core/WalletManager'; +import { ERROR_CODES, WalletKitError } from '../errors'; +import { validateFrom, validateNetwork, validateValidUntil } from '../handlers/transactionValidators'; +import { parseConnectTransactionParamContent, toTransactionRequest } from '../types/internal'; +import type { + RawBridgeEventSignMessage, + RawBridgeEventTransaction, + RawConnectTransactionParamContent, +} from '../types/internal'; +import type { ApiClient } from '../types/toncenter/ApiClient'; +import type { ValidationResult } from '../validation'; +import { validateStructuredItems, validateTransactionMessages } from '../validation/transaction'; +import { resolveItemsToMessages } from './itemsResolver'; +import { globalLogger } from '../core/Logger'; + +const log = globalLogger.createChild('EventsUtils'); + +/** + * Helper to get wallet from event + */ +export function getWalletFromEvent(walletManager: WalletManager, event: { walletId?: string }): Wallet | undefined { + if (event.walletId) { + return walletManager.getWallet(event.walletId); + } + return undefined; +} + +export function getWalletAddressFromEvent( + walletManager: WalletManager, + event: { walletId?: string; walletAddress?: string }, +): string | undefined { + if (event.walletAddress) { + return event.walletAddress; + } + if (event.walletId) { + return walletManager.getWallet(event.walletId)?.getAddress(); + } + return undefined; +} + +export function getClientForWallet(walletManager: WalletManager, event: { walletId?: string }): ApiClient { + const wallet = getWalletFromEvent(walletManager, event); + if (!wallet) { + throw new WalletKitError(ERROR_CODES.WALLET_NOT_FOUND, `Wallet not found: ${event.walletId}`); + } + + return wallet.getClient(); +} + +export async function checkTransactionRequestItems( + request: TransactionRequest, + wallet: Wallet, +): Promise { + if (!request.items || request.items.length === 0) { + return { ...request }; + } + + const newRequest = { ...request }; + newRequest.messages = await resolveItemsToMessages(request.items, wallet); + // probably we should not remove items here, it can be used for analytics and debugging + // newRequest.items = undefined; + + return newRequest; +} + +export function validateTransactionRequestForWallet( + request: TransactionRequest, + wallet: Wallet, + isLocal?: boolean, + requireNetworkAndFrom: boolean = true, +): ValidationResult { + let errors: string[] = []; + + const validUntilValidation = validateValidUntil(request.validUntil); + if (!validUntilValidation.isValid) { + errors = errors.concat(validUntilValidation.errors); + } + + if (requireNetworkAndFrom || request.network?.chainId) { + const networkValidation = validateNetwork(request.network?.chainId, wallet); + if (!networkValidation.isValid) { + errors = errors.concat(networkValidation.errors); + } + } + + if (requireNetworkAndFrom || request.fromAddress) { + const fromValidation = validateFrom(request.fromAddress, wallet); + if (!fromValidation.isValid) { + errors = errors.concat(fromValidation.errors); + } + } + + if (request.items && request.items.length > 0) { + const itemsValidation = validateStructuredItems(request.items); + if (!itemsValidation.isValid) { + errors = errors.concat(itemsValidation.errors); + } + } else { + const messagesValidation = validateTransactionMessages(request.messages ?? [], !isLocal, true); + if (!messagesValidation.isValid) { + errors = errors.concat(messagesValidation.errors); + } + } + + return { isValid: errors.length === 0, errors }; +} + +export function parseTonConnectTransactionRequest( + event: RawBridgeEventTransaction | RawBridgeEventSignMessage, + wallet: Wallet, +): { + result: TransactionRequest | undefined; + validation: ValidationResult; +} { + let errors: string[] = []; + try { + if (event.params.length !== 1) { + throw new WalletKitError( + ERROR_CODES.INVALID_REQUEST_EVENT, + 'Invalid transaction request - expected exactly 1 parameter', + undefined, + { paramCount: event.params.length, eventId: event.id }, + ); + } + const rawParams = JSON.parse(event.params[0]) as RawConnectTransactionParamContent; + const params = parseConnectTransactionParamContent(rawParams); + const request = toTransactionRequest(params); + const validation = validateTransactionRequestForWallet(request, wallet, event.isLocal); + + return { + result: request, + validation, + }; + } catch (error) { + log.error('Failed to parse transaction request', { error }); + errors.push('Failed to parse transaction request'); + return { + result: undefined, + validation: { isValid: errors.length === 0, errors: errors }, + }; + } +} diff --git a/packages/walletkit/src/utils/itemsResolver.ts b/packages/walletkit/src/utils/itemsResolver.ts new file mode 100644 index 000000000..c423c9ce3 --- /dev/null +++ b/packages/walletkit/src/utils/itemsResolver.ts @@ -0,0 +1,125 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { Address, Cell, beginCell } from '@ton/core'; + +import type { TransactionRequestMessage, StructuredItem, Base64String } from '../api/models'; +import { SendModeFlag } from '../api/models'; +import type { Wallet } from '../api/interfaces'; +import { + storeJettonTransferMessage, + storeNftTransferMessage, + DEFAULT_JETTON_GAS_FEE, + DEFAULT_NFT_GAS_FEE, + DEFAULT_FORWARD_AMOUNT, +} from './messageBuilders'; +import { globalLogger } from '../core/Logger'; + +const log = globalLogger.createChild('ItemsResolver'); + +/** + * Resolve structured items (ton/jetton/nft) into raw TransactionRequestMessages. + * After resolution, downstream code only needs to handle messages. + */ +export async function resolveItemsToMessages( + items: StructuredItem[], + wallet: Wallet, +): Promise { + const messages: TransactionRequestMessage[] = []; + + for (const item of items) { + switch (item.type) { + case 'ton': + messages.push(resolveTonItem(item)); + break; + case 'jetton': + messages.push(await resolveJettonItem(item, wallet)); + break; + case 'nft': + messages.push(resolveNftItem(item, wallet)); + break; + default: + log.warn('Unknown item type, skipping', { item }); + break; + } + } + + return messages; +} + +function resolveTonItem(item: StructuredItem & { type: 'ton' }): TransactionRequestMessage { + return { + address: item.address, + amount: item.amount, + payload: item.payload as Base64String, + stateInit: item.stateInit as Base64String, + extraCurrency: item.extraCurrency, + mode: { flags: [SendModeFlag.IGNORE_ERRORS, SendModeFlag.PAY_GAS_SEPARATELY] }, + }; +} + +async function resolveJettonItem( + item: StructuredItem & { type: 'jetton' }, + wallet: Wallet, +): Promise { + // Resolve the sender's jetton wallet address from the jetton master + const jettonWalletAddress = await wallet.getJettonWalletAddress(item.master); + + const customPayload = item.customPayload ? Cell.fromBase64(item.customPayload) : null; + const forwardPayload = item.forwardPayload ? Cell.fromBase64(item.forwardPayload) : null; + + const payload = beginCell() + .store( + storeJettonTransferMessage({ + queryId: item.queryId ? BigInt(item.queryId) : 0n, + amount: BigInt(item.amount), + destination: Address.parse(item.destination), + responseDestination: item.responseDestination + ? Address.parse(item.responseDestination) + : Address.parse(wallet.getAddress()), + customPayload, + forwardAmount: item.forwardAmount ? BigInt(item.forwardAmount) : DEFAULT_FORWARD_AMOUNT, + forwardPayload, + }), + ) + .endCell(); + + return { + address: jettonWalletAddress, + amount: item.attachAmount ?? DEFAULT_JETTON_GAS_FEE, + payload: payload.toBoc().toString('base64') as Base64String, + mode: { flags: [SendModeFlag.IGNORE_ERRORS, SendModeFlag.PAY_GAS_SEPARATELY] }, + }; +} + +function resolveNftItem(item: StructuredItem & { type: 'nft' }, wallet: Wallet): TransactionRequestMessage { + const customPayload = item.customPayload ? Cell.fromBase64(item.customPayload) : null; + const forwardPayload = item.forwardPayload ? Cell.fromBase64(item.forwardPayload) : null; + + const payload = beginCell() + .store( + storeNftTransferMessage({ + queryId: item.queryId ? BigInt(item.queryId) : 0n, + newOwner: Address.parse(item.newOwner), + responseDestination: item.responseDestination + ? Address.parse(item.responseDestination) + : Address.parse(wallet.getAddress()), + customPayload, + forwardAmount: item.forwardAmount ? BigInt(item.forwardAmount) : DEFAULT_FORWARD_AMOUNT, + forwardPayload, + }), + ) + .endCell(); + + return { + address: item.nftAddress, + amount: item.attachAmount ?? DEFAULT_NFT_GAS_FEE, + payload: payload.toBoc().toString('base64') as Base64String, + mode: { flags: [SendModeFlag.IGNORE_ERRORS, SendModeFlag.PAY_GAS_SEPARATELY] }, + }; +} diff --git a/packages/walletkit/src/utils/toncenterEmulation.ts b/packages/walletkit/src/utils/toncenterEmulation.ts index 9df9eecf3..697e5dcf2 100644 --- a/packages/walletkit/src/utils/toncenterEmulation.ts +++ b/packages/walletkit/src/utils/toncenterEmulation.ts @@ -6,8 +6,8 @@ * */ -import type { Slice } from '@ton/core'; -import { Address, Cell } from '@ton/core'; +import type { CommonMessageInfoInternal, Slice } from '@ton/core'; +import { Address, beginCell, Cell, loadMessageRelaxed, storeMessageRelaxed } from '@ton/core'; import type { EmulationTokenInfoWallets, ToncenterEmulationResponse } from '../types/toncenter/emulation'; import { toTransactionEmulatedTrace } from '../types/toncenter/emulation'; @@ -15,6 +15,7 @@ import type { ErrorInfo } from '../errors/WalletKitError'; import { ERROR_CODES } from '../errors/codes'; import { CallForSuccess } from './retry'; import type { + Base64String, TransactionEmulatedPreview, TransactionTraceMoneyFlow, TransactionTraceMoneyFlowItem, @@ -25,8 +26,14 @@ import { SendModeToValue } from './sendMode'; import type { Wallet } from '../api/interfaces'; import { asAddressFriendly, asMaybeAddressFriendly } from './address'; import type { ApiClient } from '../types/toncenter/ApiClient'; +import type { TonWalletKitOptions } from '../types/config'; +import { globalLogger } from '../core/Logger'; -// import { ConnectMessageTransactionMessage } from '@/types/connect'; +const log = globalLogger.createChild('ToncenterEmulation'); + +// For sign-message previews the wallet produces a relaxed internal message with value=0; +// emulation needs a realistic coin balance to pay gas, so we inject 2 TON here. +const SIGN_MODE_EMULATION_VALUE = 2_000_000_000n; export interface ToncenterMessage { method: string; @@ -132,10 +139,19 @@ function parseJettonTransfer(slice: Slice): JettonTransfer { }; } +export interface ProcessMoneyFlowOptions { + // When true, the first transaction's incoming message is excluded from inputs + // (used for sign-message previews where that message is a synthetic 2-TON gasless-relay wrapper). + skipFirstTxInput?: boolean; +} + /** * Processes toncenter emulation result to extract money flow */ -export function processToncenterMoneyFlow(emulation: ToncenterEmulationResponse): TransactionTraceMoneyFlow { +export function processToncenterMoneyFlow( + emulation: ToncenterEmulationResponse, + options: ProcessMoneyFlowOptions = {}, +): TransactionTraceMoneyFlow { if (!emulation || !emulation.transactions) { return { outputs: '0', @@ -152,7 +168,8 @@ export function processToncenterMoneyFlow(emulation: ToncenterEmulationResponse) const ourTxes = Object.values(emulation.transactions).filter((t) => t.account === firstTx.account); const messagesFrom = ourTxes.flatMap((t) => t.out_msgs); - const messagesTo = ourTxes.flatMap((t) => t.in_msg).filter((m) => m !== null); + const incomingTxes = options.skipFirstTxInput ? ourTxes.filter((t) => t.hash !== firstTx.hash) : ourTxes; + const messagesTo = incomingTxes.flatMap((t) => t.in_msg).filter((m) => m !== null); // Calculate TON outputs const outputs = messagesFrom @@ -275,6 +292,25 @@ export function processToncenterMoneyFlow(emulation: ToncenterEmulationResponse) }; } +export type TransactionPreviewMode = 'send' | 'sign'; + +export interface TransactionPreviewOptions { + // 'send' emulates the external message as-is; 'sign' emulates the internal body + mode?: TransactionPreviewMode; + relayGas?: bigint; // gas amount to inject for gasless relaying, by default 2 TON +} + +function wrapInternalForSignEmulation(relaxedBoc: Base64String, options: TransactionPreviewOptions): Base64String { + const cell = Cell.fromBase64(relaxedBoc); + const message = loadMessageRelaxed(cell.beginParse()); + if (message.info.type !== 'internal') { + throw new Error('Expected relaxed internal message for sign-mode emulation'); + } + const info = message.info as CommonMessageInfoInternal; + info.value = { coins: options.relayGas ?? SIGN_MODE_EMULATION_VALUE }; + return beginCell().store(storeMessageRelaxed(message)).endCell().toBoc().toString('base64') as Base64String; +} + /** * Creates a transaction preview by emulating the transaction */ @@ -282,10 +318,17 @@ export async function createTransactionPreview( client: ApiClient, request: TransactionRequest, wallet?: Wallet, + options: TransactionPreviewOptions = {}, ): Promise { - const txData = await wallet?.getSignedSendTransaction(request, { fakeSignature: true }); + const mode: TransactionPreviewMode = options.mode ?? 'send'; + const isSignMode = mode === 'sign'; + + const signedBoc = await wallet?.getSignedSendTransaction(request, { + fakeSignature: true, + internal: isSignMode, + }); - if (!txData) { + if (!signedBoc) { return { result: Result.failure, error: { @@ -295,9 +338,11 @@ export async function createTransactionPreview( }; } + const bocForEmulation = isSignMode ? wrapInternalForSignEmulation(signedBoc, options) : signedBoc; + let emulationResult: ToncenterEmulationResponse; try { - const emulatedResult = await CallForSuccess(() => client.fetchEmulation(txData, true)); + const emulatedResult = await CallForSuccess(() => client.fetchEmulation(bocForEmulation, true)); if (emulatedResult.result === 'success') { emulationResult = emulatedResult.emulationResult; } else { @@ -319,7 +364,7 @@ export async function createTransactionPreview( }; } - const moneyFlow = processToncenterMoneyFlow(emulationResult); + const moneyFlow = processToncenterMoneyFlow(emulationResult, { skipFirstTxInput: isSignMode }); return { result: Result.success, @@ -327,3 +372,31 @@ export async function createTransactionPreview( moneyFlow, }; } + +export async function createTransactionPreviewIfPossible( + config: TonWalletKitOptions, + client: ApiClient, + request: TransactionRequest, + wallet?: Wallet, + options: TransactionPreviewOptions = {}, +): Promise { + if (config.eventProcessor?.disableTransactionEmulation) { + return undefined; + } + + let preview: TransactionEmulatedPreview | undefined; + try { + preview = await CallForSuccess(() => createTransactionPreview(client, request, wallet, options)); + } catch (error) { + log.error('Failed to create transaction preview', { error }); + preview = { + error: { + code: ERROR_CODES.UNKNOWN_EMULATION_ERROR, + message: 'Unknown emulation error', + }, + result: Result.failure, + }; + } + + return preview; +} diff --git a/packages/walletkit/src/validation/events.ts b/packages/walletkit/src/validation/events.ts index 74d631e62..c2c744696 100644 --- a/packages/walletkit/src/validation/events.ts +++ b/packages/walletkit/src/validation/events.ts @@ -151,7 +151,9 @@ function isValidEventMethod(method: string): boolean { 'connect', 'sendTransaction', 'signData', + 'signMessage', 'disconnect', + 'restoreConnection', 'tonconnect_connect', 'tonconnect_sendTransaction', 'tonconnect_signData', diff --git a/packages/walletkit/src/validation/transaction.ts b/packages/walletkit/src/validation/transaction.ts index 6509020e7..4c762f538 100644 --- a/packages/walletkit/src/validation/transaction.ts +++ b/packages/walletkit/src/validation/transaction.ts @@ -10,7 +10,7 @@ import { Cell } from '@ton/core'; import type { ValidationResult } from './types'; import { validateTonAddress } from './address'; -import { isFriendlyTonAddress } from '../utils/address'; +import { isFriendlyTonAddress, isValidAddress } from '../utils/address'; /** * Human-readable transaction message @@ -36,8 +36,12 @@ export interface HumanReadableTx { /** * Validate transaction messages array */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function validateTransactionMessages(messages: any[], isTonConnect: boolean = true): ValidationResult { + +export function validateTransactionMessages( + messages: unknown[], + isTonConnect: boolean = true, + requireFriendlyAddress: boolean = true, +): ValidationResult { const errors: string[] = []; if (!Array.isArray(messages)) { @@ -52,7 +56,7 @@ export function validateTransactionMessages(messages: any[], isTonConnect: boole // Validate each message messages.forEach((msg, index) => { - const msgErrors = validateTransactionMessage(msg, isTonConnect).errors; + const msgErrors = validateTransactionMessage(msg, isTonConnect, requireFriendlyAddress).errors; msgErrors.forEach((error) => { errors.push(`message[${index}]: ${error}`); }); @@ -67,8 +71,12 @@ export function validateTransactionMessages(messages: any[], isTonConnect: boole /** * Validate individual transaction message */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function validateTransactionMessage(message: any, isTonConnect: boolean = true): ValidationResult { + +export function validateTransactionMessage( + message: unknown, + isTonConnect: boolean = true, + requireFriendlyAddress: boolean = true, +): ValidationResult { const errors: string[] = []; if (typeof message !== 'object') { @@ -79,12 +87,12 @@ export function validateTransactionMessage(message: any, isTonConnect: boolean = return { isValid: false, errors: ['Invalid message'] }; } - if (isTonConnect && typeof message.mode !== 'undefined') { + if (isTonConnect && 'mode' in message && typeof message.mode !== 'undefined') { errors.push('mode must be undefined for tonconnect!'); } // Object format - validate required fields - const objErrors = validateMessageObject(message).errors; + const objErrors = validateMessageObject(message, requireFriendlyAddress).errors; errors.push(...objErrors); return { @@ -97,15 +105,19 @@ export function validateTransactionMessage(message: any, isTonConnect: boolean = * Validate message object structure */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function validateMessageObject(message: any): ValidationResult { +export function validateMessageObject(message: any, requireFriendlyAddress: boolean = true): ValidationResult { const errors: string[] = []; // Required fields if (!message.address || typeof message.address !== 'string') { errors.push('to address is required and must be a string'); } else { - if (!isFriendlyTonAddress(message.address)) { - errors.push('to address must be a valid friendly TON address'); + if (requireFriendlyAddress ? !isFriendlyTonAddress(message.address) : !isValidAddress(message.address)) { + errors.push( + requireFriendlyAddress + ? 'to address must be a valid friendly TON address' + : 'to address must be a valid TON address', + ); } } @@ -212,6 +224,74 @@ export function validateBOC(bocString: string): ValidationResult { }; } +/** + * Validate structured items array (ton/jetton/nft) + */ +export function validateStructuredItems(items: unknown[]): ValidationResult { + const errors: string[] = []; + + if (!Array.isArray(items)) { + errors.push('items must be an array'); + return { isValid: false, errors }; + } + + if (items.length === 0) { + errors.push('items array cannot be empty'); + return { isValid: false, errors }; + } + + items.forEach((item, index) => { + if (!item || typeof item !== 'object') { + errors.push(`item[${index}]: must be an object`); + return; + } + + const obj = item as Record; + + if (!obj.type || typeof obj.type !== 'string') { + errors.push(`item[${index}]: type is required`); + return; + } + + switch (obj.type) { + case 'ton': + if (!obj.address || typeof obj.address !== 'string') { + errors.push(`item[${index}]: ton item requires address`); + } + if (!obj.amount || typeof obj.amount !== 'string') { + errors.push(`item[${index}]: ton item requires amount`); + } + break; + case 'jetton': + if (!obj.master || typeof obj.master !== 'string') { + errors.push(`item[${index}]: jetton item requires master`); + } + if (!obj.destination || typeof obj.destination !== 'string') { + errors.push(`item[${index}]: jetton item requires destination`); + } + if (!obj.amount || typeof obj.amount !== 'string') { + errors.push(`item[${index}]: jetton item requires amount`); + } + break; + case 'nft': + if (!obj.nftAddress || typeof obj.nftAddress !== 'string') { + errors.push(`item[${index}]: nft item requires nftAddress`); + } + if (!obj.newOwner || typeof obj.newOwner !== 'string') { + errors.push(`item[${index}]: nft item requires newOwner`); + } + break; + default: + errors.push(`item[${index}]: unknown item type '${obj.type}'`); + } + }); + + return { + isValid: errors.length === 0, + errors, + }; +} + /** * Check if value is a valid nanonton amount */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 578b65fed..9299dedda 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,21 +18,9 @@ catalogs: '@ton/crypto': specifier: 3.3.0 version: 3.3.0 - '@tonconnect/protocol': - specifier: 2.4.0 - version: 2.4.0 - '@tonconnect/sdk': - specifier: 3.4.1 - version: 3.4.1 - '@tonconnect/ui': - specifier: 2.4.4 - version: 2.4.4 - '@tonconnect/ui-react': - specifier: 2.4.4 - version: 2.4.4 '@types/react': - specifier: 19.2.14 - version: 19.2.14 + specifier: 19.2.3 + version: 19.2.3 '@types/react-dom': specifier: 19.2.3 version: 19.2.3 @@ -53,6 +41,12 @@ catalogs: specifier: 0.81.5 version: 0.81.5 +overrides: + '@tonconnect/protocol': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz + '@tonconnect/sdk': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz + '@tonconnect/ui': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz + '@tonconnect/ui-react': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz + importers: .: @@ -103,11 +97,11 @@ importers: specifier: 'catalog:' version: 3.3.0 '@tonconnect/sdk': - specifier: 'catalog:' - version: 3.4.1 + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz '@tonconnect/ui-react': - specifier: 'catalog:' - version: 2.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz(react-dom@19.2.3(react@19.2.3))(react@19.2.3) buffer: specifier: ^6.0.3 version: 6.0.3 @@ -146,17 +140,17 @@ importers: version: 1.4.0 zustand: specifier: ^5.0.12 - version: 5.0.12(@types/react@19.2.14)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + version: 5.0.12(@types/react@19.2.3)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: '@eslint/js': specifier: ^10.0.1 version: 10.0.1(eslint@9.39.4(jiti@2.6.1)) '@types/react': specifier: 'catalog:' - version: 19.2.14 + version: 19.2.3 '@types/react-dom': specifier: 'catalog:' - version: 19.2.3(@types/react@19.2.14) + version: 19.2.3(@types/react@19.2.3) '@vitejs/plugin-react': specifier: ^6.0.1 version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) @@ -270,7 +264,7 @@ importers: version: 1.4.0 zustand: specifier: ^5.0.12 - version: 5.0.12(@types/react@19.2.14)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + version: 5.0.12(@types/react@19.2.3)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: '@eslint/js': specifier: ^10.0.1 @@ -283,10 +277,10 @@ importers: version: 0.1.40 '@types/react': specifier: 'catalog:' - version: 19.2.14 + version: 19.2.3 '@types/react-dom': specifier: 'catalog:' - version: 19.2.3(@types/react@19.2.14) + version: 19.2.3(@types/react@19.2.3) '@types/webextension-polyfill': specifier: ^0.12.4 version: 0.12.5 @@ -520,14 +514,14 @@ importers: specifier: workspace:* version: link:../../packages/walletkit '@tonconnect/sdk': - specifier: 'catalog:' - version: 3.4.1 + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz '@tonconnect/ui-react': - specifier: 'catalog:' - version: 2.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@types/react': specifier: 'catalog:' - version: 19.2.14 + version: 19.2.3 dotenv: specifier: ^17.4.2 version: 17.4.2 @@ -549,7 +543,7 @@ importers: version: 10.4.1 '@testing-library/react': specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@vitest/coverage-v8': specifier: ^4.1.4 version: 4.1.4(vitest@4.1.4) @@ -575,8 +569,8 @@ importers: specifier: workspace:* version: link:../../packages/walletkit '@tonconnect/protocol': - specifier: 'catalog:' - version: 2.4.0 + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz devDependencies: '@ledgerhq/hw-transport-node-hid': specifier: 6.30.0 @@ -613,11 +607,11 @@ importers: version: 19.2.3 zustand: specifier: ^5.0.8 - version: 5.0.10(@types/react@19.2.14)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + version: 5.0.10(@types/react@19.2.3)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: '@types/react': specifier: 'catalog:' - version: 19.2.14 + version: 19.2.3 rimraf: specifier: ^6.1.2 version: 6.1.3 @@ -633,6 +627,9 @@ importers: '@ton/walletkit': specifier: workspace:* version: link:../walletkit + '@tonconnect/ui': + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz devDependencies: '@tanstack/query-core': specifier: 'catalog:' @@ -640,9 +637,6 @@ importers: '@ton/core': specifier: 'catalog:' version: 0.63.1(@ton/crypto@3.3.0) - '@tonconnect/ui': - specifier: 'catalog:' - version: 2.4.4 typescript: specifier: ~5.9.3 version: 5.9.3 @@ -656,21 +650,24 @@ importers: specifier: workspace:* version: link:../appkit '@tonconnect/ui': - specifier: '>=2.4.1' - version: 2.4.1 + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz + '@tonconnect/ui-react': + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: specifier: 2.1.1 version: 2.1.1 radix-ui: specifier: ^1.4.3 - version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rosetta: specifier: 1.1.0 version: 1.1.0 devDependencies: '@storybook/addon-docs': specifier: 10.3.5 - version: 10.3.5(@types/react@19.2.14)(esbuild@0.27.7)(rollup@4.59.0)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 10.3.5(@types/react@19.2.3)(esbuild@0.27.7)(rollup@4.59.0)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) '@storybook/react': specifier: 10.3.5 version: 10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3) @@ -683,15 +680,12 @@ importers: '@tanstack/react-query': specifier: 'catalog:' version: 5.99.0(react@19.2.3) - '@tonconnect/ui-react': - specifier: 'catalog:' - version: 2.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@types/react': specifier: 'catalog:' - version: 19.2.14 + version: 19.2.3 '@types/react-dom': specifier: 'catalog:' - version: 19.2.3(@types/react@19.2.14) + version: 19.2.3(@types/react@19.2.3) copyfiles: specifier: 2.4.1 version: 2.4.1 @@ -769,11 +763,11 @@ importers: specifier: ^0.2.8 version: 0.2.8 '@tonconnect/protocol': - specifier: 'catalog:' - version: 2.4.0 + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz '@tonconnect/sdk': - specifier: 'catalog:' - version: 3.4.1 + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz lru-cache: specifier: ^11.3.5 version: 11.3.5 @@ -867,8 +861,8 @@ importers: specifier: ^0.2.4 version: 0.2.4 '@tonconnect/protocol': - specifier: 'catalog:' - version: 2.4.0 + specifier: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz + version: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz buffer: specifier: ^6.0.3 version: 6.0.3 @@ -3905,23 +3899,24 @@ packages: '@tonconnect/isomorphic-fetch@0.0.3': resolution: {integrity: sha512-jIg5nTrDwnite4fXao3dD83eCpTvInTjZon/rZZrIftIegh4XxyVb5G2mpMqXrVGk1e8SVXm3Kj5OtfMplQs0w==} - '@tonconnect/protocol@2.4.0': - resolution: {integrity: sha512-3xg6sMWIrSgW7/f7iPgOb2BI3LaMScjDMqfu20fcEMbOVvNFk3TGAUKK5cJ3pfvUINsyLgPoylcIbPap37jXiA==} + '@tonconnect/protocol@https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz': + resolution: {tarball: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz} + version: 2.4.0 - '@tonconnect/sdk@3.4.1': - resolution: {integrity: sha512-eVH8erAFout89gXYHXua/es+mmLPiW1r7ng9hgHKYQv85HLq8/zzQEkbJpbTlnIuQ6CJ/QKchjMsIgYz3BYCUQ==} + '@tonconnect/sdk@https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz': + resolution: {tarball: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz} + version: 3.4.1 - '@tonconnect/ui-react@2.4.4': - resolution: {integrity: sha512-4qpAABZK+nsJSQPGAVsj6xPtQ6HkqGIn/M984dNgp5eqVoaVHti0rCMOqcTutb3BScAvWLJoo3hONqNIg2fuEQ==} + '@tonconnect/ui-react@https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz': + resolution: {tarball: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz} + version: 2.4.4 peerDependencies: react: '>=17.0.0' react-dom: '>=17.0.0' - '@tonconnect/ui@2.4.1': - resolution: {integrity: sha512-W0wIDJdDdYV3rqP0vt92H/5HktkjeEAddhAOBo0LKZzrMG0r9obY00FKpuV/i2re9RAXLGq/oMwBRZaRHer4kA==} - - '@tonconnect/ui@2.4.4': - resolution: {integrity: sha512-XgIHAkdimDk+YeRDPsQp7eOz2lM4aScYL3hiTP1Z9YgWRVROjEqTbw5ZP9pt6rgmYTzL26QpqhI6uNTFdYNFIA==} + '@tonconnect/ui@https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz': + resolution: {tarball: https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz} + version: 2.4.4 '@truecarry/vite-plugin-web-extension@4.5.1': resolution: {integrity: sha512-WSramCYE+wVFH/zQdBhaBP5gSd9pldyjOu+jvkaTEFXwzCDbNQJ+34ke4/XDsaohpcQTe+lbVXbUT+Jt4Ni9Jw==} @@ -4058,6 +4053,9 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/react@19.2.3': + resolution: {integrity: sha512-k5dJVszUiNr1DSe8Cs+knKR6IrqhqdhpUwzqhkS8ecQTSf3THNtbfIp/umqHMpX2bv+9dkx3fwDv/86LcSfvSg==} + '@types/resolve@1.20.6': resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} @@ -4217,6 +4215,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -12009,10 +12008,10 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.3)': + '@mdx-js/react@3.1.1(@types/react@19.2.3)(react@19.2.3)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 19.2.14 + '@types/react': 19.2.3 react: 19.2.3 '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': @@ -12100,108 +12099,108 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)': dependencies: @@ -12215,17 +12214,17 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12233,25 +12232,25 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.3)(react@19.2.3)': dependencies: react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 - '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12259,11 +12258,11 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.3)(react@19.2.3)': dependencies: react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)': dependencies: @@ -12287,27 +12286,27 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) aria-hidden: 1.2.6 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.3) + react-remove-scroll: 2.7.2(@types/react@19.2.3)(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12315,11 +12314,11 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.3)(react@19.2.3)': dependencies: react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)': dependencies: @@ -12334,33 +12333,33 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12368,11 +12367,11 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.3)(react@19.2.3)': dependencies: react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)': dependencies: @@ -12385,47 +12384,47 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12434,164 +12433,164 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.3)(react@19.2.3)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 - '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) aria-hidden: 1.2.6 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.3) + react-remove-scroll: 2.7.2(@types/react@19.2.3)(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) aria-hidden: 1.2.6 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.3) + react-remove-scroll: 2.7.2(@types/react@19.2.3)(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@floating-ui/react-dom': 2.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.3)(react@19.2.3) '@radix-ui/rect': 1.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)': dependencies: @@ -12603,15 +12602,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)': dependencies: @@ -12623,15 +12622,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)': dependencies: @@ -12642,42 +12641,42 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)': dependencies: @@ -12696,96 +12695,96 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) aria-hidden: 1.2.6 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.3) + react-remove-scroll: 2.7.2(@types/react@19.2.3)(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-slot@1.2.0(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12801,27 +12800,27 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.3)(react@19.2.3)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)': dependencies: @@ -12839,102 +12838,102 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12942,11 +12941,11 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.3)(react@19.2.3)': dependencies: react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12956,13 +12955,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.3)(react@19.2.3)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12971,12 +12970,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.3)(react@19.2.3)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -12985,19 +12984,19 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.3)(react@19.2.3)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 - '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.3)(react@19.2.3)': dependencies: react: 19.2.3 use-sync-external-store: 1.6.0(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.1.0)': dependencies: @@ -13005,40 +13004,40 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.3)(react@19.2.3)': dependencies: react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.3)(react@19.2.3)': dependencies: react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.3)(react@19.2.3)': dependencies: '@radix-ui/rect': 1.1.1 react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.3)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.3)(react@19.2.3)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) react: 19.2.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@radix-ui/rect@1.1.1': {} @@ -13415,9 +13414,9 @@ snapshots: - bufferutil - utf-8-validate - '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.27.7)(rollup@4.59.0)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': + '@storybook/addon-docs@10.3.5(@types/react@19.2.3)(esbuild@0.27.7)(rollup@4.59.0)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.3) + '@mdx-js/react': 3.1.1(@types/react@19.2.3)(react@19.2.3) '@storybook/csf-plugin': 10.3.5(esbuild@0.27.7)(rollup@4.59.0)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) '@storybook/icons': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) @@ -13699,15 +13698,15 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@babel/runtime': 7.28.6 '@testing-library/dom': 10.4.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': dependencies: @@ -13773,7 +13772,7 @@ snapshots: dependencies: '@tonconnect/isomorphic-eventsource': 0.0.2 '@tonconnect/isomorphic-fetch': 0.0.3 - '@tonconnect/protocol': 2.4.0 + '@tonconnect/protocol': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz transitivePeerDependencies: - encoding @@ -13781,7 +13780,7 @@ snapshots: dependencies: '@tonconnect/isomorphic-eventsource': 0.0.2 '@tonconnect/isomorphic-fetch': 0.0.3 - '@tonconnect/protocol': 2.4.0 + '@tonconnect/protocol': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz blakejs: 1.2.1 tweetnacl: 1.0.3 transitivePeerDependencies: @@ -13797,40 +13796,30 @@ snapshots: transitivePeerDependencies: - encoding - '@tonconnect/protocol@2.4.0': + '@tonconnect/protocol@https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz': dependencies: tweetnacl: 1.0.3 tweetnacl-util: 0.15.1 - '@tonconnect/sdk@3.4.1': + '@tonconnect/sdk@https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz': dependencies: '@tonconnect/isomorphic-eventsource': 0.0.2 '@tonconnect/isomorphic-fetch': 0.0.3 - '@tonconnect/protocol': 2.4.0 + '@tonconnect/protocol': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz transitivePeerDependencies: - encoding - '@tonconnect/ui-react@2.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tonconnect/ui-react@https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@tonconnect/ui': 2.4.4 + '@tonconnect/ui': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz react: 19.2.3 react-dom: 19.2.3(react@19.2.3) transitivePeerDependencies: - encoding - '@tonconnect/ui@2.4.1': + '@tonconnect/ui@https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz': dependencies: - '@tonconnect/sdk': 3.4.1 - classnames: 2.5.1 - csstype: 3.2.3 - deepmerge: 4.3.1 - ua-parser-js: 1.0.41 - transitivePeerDependencies: - - encoding - - '@tonconnect/ui@2.4.4': - dependencies: - '@tonconnect/sdk': 3.4.1 + '@tonconnect/sdk': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz classnames: 2.5.1 csstype: 3.2.3 deepmerge: 4.3.1 @@ -13990,11 +13979,20 @@ snapshots: '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 + optional: true + + '@types/react-dom@19.2.3(@types/react@19.2.3)': + dependencies: + '@types/react': 19.2.3 '@types/react@19.2.14': dependencies: csstype: 3.2.3 + '@types/react@19.2.3': + dependencies: + csstype: 3.2.3 + '@types/resolve@1.20.6': {} '@types/stack-utils@2.0.3': {} @@ -18845,68 +18843,68 @@ snapshots: quick-format-unescaped@4.0.4: {} - radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.3) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.3)(react@19.2.3) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.3 + '@types/react-dom': 19.2.3(@types/react@19.2.3) randombytes@2.1.0: dependencies: @@ -19188,13 +19186,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.3): + react-remove-scroll-bar@2.3.8(@types/react@19.2.3)(react@19.2.3): dependencies: react: 19.2.3 - react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.3) + react-style-singleton: 2.2.3(@types/react@19.2.3)(react@19.2.3) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.1.0): dependencies: @@ -19207,16 +19205,16 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.3): + react-remove-scroll@2.7.2(@types/react@19.2.3)(react@19.2.3): dependencies: react: 19.2.3 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.3) - react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.3) + react-remove-scroll-bar: 2.3.8(@types/react@19.2.3)(react@19.2.3) + react-style-singleton: 2.2.3(@types/react@19.2.3)(react@19.2.3) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.3) - use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.3) + use-callback-ref: 1.3.3(@types/react@19.2.3)(react@19.2.3) + use-sidecar: 1.1.3(@types/react@19.2.3)(react@19.2.3) optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 react-router-dom@7.13.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: @@ -19240,13 +19238,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.3): + react-style-singleton@2.2.3(@types/react@19.2.3)(react@19.2.3): dependencies: get-nonce: 1.0.1 react: 19.2.3 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 react@19.1.0: {} @@ -20505,12 +20503,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.3): + use-callback-ref@1.3.3(@types/react@19.2.3)(react@19.2.3): dependencies: react: 19.2.3 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 use-latest-callback@0.2.6(react@19.1.0): dependencies: @@ -20524,13 +20522,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.3): + use-sidecar@1.1.3(@types/react@19.2.3)(react@19.2.3): dependencies: detect-node-es: 1.1.0 react: 19.2.3 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 use-sync-external-store@1.6.0(react@19.1.0): dependencies: @@ -20991,9 +20989,9 @@ snapshots: zod@4.3.6: {} - zustand@5.0.10(@types/react@19.2.14)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): + zustand@5.0.10(@types/react@19.2.3)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 immer: 10.2.0 react: 19.2.3 use-sync-external-store: 1.6.0(react@19.2.3) @@ -21005,9 +21003,9 @@ snapshots: react: 19.1.0 use-sync-external-store: 1.6.0(react@19.1.0) - zustand@5.0.12(@types/react@19.2.14)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): + zustand@5.0.12(@types/react@19.2.3)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.3 immer: 10.2.0 react: 19.2.3 use-sync-external-store: 1.6.0(react@19.2.3) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 582e16182..c72b543dc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,11 +8,11 @@ catalog: '@tanstack/react-query': 5.99.0 '@ton/core': 0.63.1 '@ton/crypto': 3.3.0 - '@tonconnect/protocol': 2.4.0 - '@tonconnect/sdk': 3.4.1 - '@tonconnect/ui': 2.4.4 - '@tonconnect/ui-react': 2.4.4 - '@types/react': 19.2.14 + '@tonconnect/protocol': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-protocol-2.4.0.tgz + '@tonconnect/sdk': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-sdk-3.4.1.tgz + '@tonconnect/ui': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-2.4.4.tgz + '@tonconnect/ui-react': https://github.com/ton-connect/sdk/releases/download/2.5.0-alpha.2/tonconnect-ui-react-2.4.4.tgz + '@types/react': 19.2.3 '@types/react-dom': 19.2.3 react: 19.2.3 react-dom: 19.2.3