diff --git a/apps/laboratory/package.json b/apps/laboratory/package.json index 1c7ba9538b..0b809a2554 100644 --- a/apps/laboratory/package.json +++ b/apps/laboratory/package.json @@ -89,12 +89,14 @@ "@reown/appkit-solana": "workspace:*", "@reown/appkit-wagmi": "workspace:*", "@reown/appkit-wallet": "workspace:*", + "@reown/appkit-experimental": "workspace:*", "@reown/appkit-adapter-solana": "workspace:*", "@reown/appkit-adapter-ethers": "workspace:*", "@reown/appkit-adapter-ethers5": "workspace:*", "@reown/appkit-adapter-wagmi": "workspace:*", "axios": "1.7.2", "bs58": "6.0.0", + "date-fns": "4.1.0", "ethers": "6.13.2", "ethers5": "npm:ethers@5.7.2", "framer-motion": "10.17.9", diff --git a/apps/laboratory/src/components/SmartSessionGrantedPermissionsInfo.tsx b/apps/laboratory/src/components/SmartSessionGrantedPermissionsInfo.tsx new file mode 100644 index 0000000000..9b87d2cb2b --- /dev/null +++ b/apps/laboratory/src/components/SmartSessionGrantedPermissionsInfo.tsx @@ -0,0 +1,47 @@ +import * as React from 'react' +import { StackDivider, Heading, Box, Stack, Text } from '@chakra-ui/react' +import { getChain } from '../utils/NetworksUtil' +import { formatDistanceToNow } from 'date-fns' +import type { SmartSessionGrantPermissionsResponse } from '@reown/appkit-experimental/smart-session' + +export function SmartSessionGrantedPermissionsInfo({ + grantedPermissions +}: { + grantedPermissions: SmartSessionGrantPermissionsResponse | undefined +}) { + if (!grantedPermissions?.context) { + return ( + + Dapp does not have any stored permissions + + ) + } + + const { address, chainId, expiry } = grantedPermissions + const parsedExpiry = formatDistanceToNow(new Date(expiry * 1000), { addSuffix: true }) + const parsedChainId = parseInt(chainId, 16) + const chain = getChain(parsedChainId) + + return ( + } spacing="4"> + + + Account Address + + {address} + + + + Chain Id + + {chain?.name || chainId} + + + + Expiry + + {parsedExpiry} + + + ) +} diff --git a/apps/laboratory/src/components/Wagmi/WagmiPermissionsAsyncTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiPermissionsAsyncTest.tsx index 63102882cb..d927710089 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiPermissionsAsyncTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiPermissionsAsyncTest.tsx @@ -1,8 +1,14 @@ import { Box, Card, CardBody, CardHeader, Heading, Stack, StackDivider } from '@chakra-ui/react' import { WagmiRequestPermissionsAsyncTest } from './WagmiRequestPermissionsAsyncTest' import { WagmiPurchaseDonutAsyncPermissionsTest } from './WagmiPurchaseDonutAsyncPermissionsTest' +import { SmartSessionGrantedPermissionsInfo } from '../SmartSessionGrantedPermissionsInfo' +import { useERC7715Permissions } from '../../hooks/useERC7715Permissions' export function WagmiPermissionsAsyncTest() { + const { smartSession } = useERC7715Permissions() + const grantedPermissions = + smartSession?.type === 'async' ? smartSession.grantedPermissions : undefined + return ( @@ -10,6 +16,12 @@ export function WagmiPermissionsAsyncTest() { } spacing="4"> + + + Existing Session Information + + + Request Permissions diff --git a/apps/laboratory/src/components/Wagmi/WagmiPermissionsSyncTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiPermissionsSyncTest.tsx index 852c1252bb..8fa4e17fd4 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiPermissionsSyncTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiPermissionsSyncTest.tsx @@ -2,8 +2,14 @@ import { Box, Card, CardBody, CardHeader, Heading, Stack, StackDivider } from '@ import { WagmiRequestPermissionsSyncTest } from './WagmiRequestPermissionsSyncTest' import { WagmiPurchaseDonutSyncPermissionsTest } from './WagmiPurchaseDonutSyncPermissionsTest' import { WagmiCreatePasskeySignerTest } from './WagmiCreatePasskeySignerTest' +import { SmartSessionGrantedPermissionsInfo } from '../SmartSessionGrantedPermissionsInfo' +import { useERC7715Permissions } from '../../hooks/useERC7715Permissions' export function WagmiPermissionsSyncTest() { + const { smartSession } = useERC7715Permissions() + const grantedPermissions = + smartSession?.type === 'sync' ? smartSession.grantedPermissions : undefined + return ( @@ -11,6 +17,12 @@ export function WagmiPermissionsSyncTest() { } spacing="4"> + + + Existing Session Information + + + New Passkey diff --git a/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutAsyncPermissionsTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutAsyncPermissionsTest.tsx index fb26dc97f0..bc88a2066f 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutAsyncPermissionsTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutAsyncPermissionsTest.tsx @@ -4,15 +4,34 @@ import { useState } from 'react' import { useChakraToast } from '../Toast' import { encodeFunctionData, parseEther } from 'viem' import { abi as donutContractAbi, address as donutContractaddress } from '../../utils/DonutContract' -import { sepolia } from 'viem/chains' import { useLocalEcdsaKey } from '../../context/LocalEcdsaKeyContext' import { useERC7715Permissions } from '../../hooks/useERC7715Permissions' -import { executeActionsWithECDSAAndCosignerPermissions } from '../../utils/ERC7715Utils' +import { executeActionsWithECDSAKey } from '../../utils/ERC7715Utils' +import { getChain } from '../../utils/NetworksUtil' +import type { SmartSessionGrantPermissionsResponse } from '@reown/appkit-experimental/smart-session' export function WagmiPurchaseDonutAsyncPermissionsTest() { - const { privateKey } = useLocalEcdsaKey() + const { smartSession } = useERC7715Permissions() + + if (smartSession?.type !== 'async' || !smartSession.grantedPermissions?.context) { + return ( + + Dapp does not have the permissions + + ) + } - const { grantedPermissions, pci } = useERC7715Permissions() + return +} + +function ConnectedTestContent({ + grantedPermissions +}: { + grantedPermissions: SmartSessionGrantPermissionsResponse +}) { + const { privateKey } = useLocalEcdsaKey() + const toast = useChakraToast() + const [isTransactionPending, setTransactionPending] = useState(false) const { data: donutsOwned, @@ -23,24 +42,24 @@ export function WagmiPurchaseDonutAsyncPermissionsTest() { abi: donutContractAbi, address: donutContractaddress, functionName: 'getBalance', - args: [grantedPermissions?.signerData?.submitToAddress || '0x'] + args: [grantedPermissions.address] }) - const [isTransactionPending, setTransactionPending] = useState(false) - const toast = useChakraToast() - async function onPurchaseDonutWithPermissions() { setTransactionPending(true) try { - if (!privateKey) { - throw new Error(`Unable to get dApp private key`) + const chainId = parseInt(grantedPermissions.chainId, 16) + if (!chainId) { + throw new Error('Chain ID not available in granted permissions') } - if (!grantedPermissions) { - throw Error('No permissions available') + const chain = getChain(chainId) + if (!chain) { + throw new Error('Unknown chainId') } - if (!pci) { - throw Error('No WC cosigner data(PCI) available') + if (!privateKey) { + throw new Error(`Unable to get dApp private key`) } + const purchaseDonutCallData = encodeFunctionData({ abi: donutContractAbi, functionName: 'purchase', @@ -53,12 +72,12 @@ export function WagmiPurchaseDonutAsyncPermissionsTest() { data: purchaseDonutCallData } ] - const txHash = await executeActionsWithECDSAAndCosignerPermissions({ + const txHash = await executeActionsWithECDSAKey({ actions: purchaseDonutCallDataExecution, - chain: sepolia, + chain, ecdsaPrivateKey: privateKey as `0x${string}`, - permissions: grantedPermissions, - pci + accountAddress: grantedPermissions.address, + permissionsContext: grantedPermissions.context }) if (txHash) { toast({ @@ -77,16 +96,9 @@ export function WagmiPurchaseDonutAsyncPermissionsTest() { } setTransactionPending(false) } - if (!grantedPermissions) { - return ( - - Dapp does not have the permissions - - ) - } return ( - + diff --git a/apps/laboratory/src/components/Wagmi/WagmiRequestPermissionsSyncTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiRequestPermissionsSyncTest.tsx index fe177cadbc..e35c13713a 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiRequestPermissionsSyncTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiRequestPermissionsSyncTest.tsx @@ -1,37 +1,32 @@ import { Button, Stack, Text } from '@chakra-ui/react' -import { useAccount } from 'wagmi' -import { walletActionsErc7715 } from 'viem/experimental' -import { useCallback, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useChakraToast } from '../Toast' -import { createWalletClient, custom, type Address, type Chain } from 'viem' -import { EIP_7715_RPC_METHODS } from '../../utils/EIP5792Utils' +import { toHex, type Address } from 'viem' import { usePasskey } from '../../context/PasskeyContext' -import { - useWagmiAvailableCapabilities, - type Provider -} from '../../hooks/useWagmiActiveCapabilities' import { useERC7715Permissions } from '../../hooks/useERC7715Permissions' import { bigIntReplacer } from '../../utils/CommonUtils' import { getPurchaseDonutPermissions } from '../../utils/ERC7715Utils' import { serializePublicKey, type P256Credential } from 'webauthn-p256' -import { KeyTypes } from '../../utils/EncodingUtils' -import { useAppKitAccount } from '@reown/appkit/react' +import { useAppKitAccount, useAppKitNetwork } from '@reown/appkit/react' +import { + grantPermissions, + isSmartSessionSupported, + type SmartSessionGrantPermissionsRequest +} from '@reown/appkit-experimental/smart-session' export function WagmiRequestPermissionsSyncTest() { - const { provider, supported } = useWagmiAvailableCapabilities({ - method: EIP_7715_RPC_METHODS.WALLET_GRANT_PERMISSIONS - }) const { address, isConnected } = useAppKitAccount() - const { chain } = useAccount() + const { chainId } = useAppKitNetwork() + const isSupported = useMemo(() => isSmartSessionSupported(), [address]) - if (!isConnected || !provider || !address || !chain) { + if (!isConnected || !address || !chainId) { return ( Wallet not connected ) } - if (!supported) { + if (!isSupported) { return ( Wallet does not support wallet_grantPermissions rpc method @@ -39,21 +34,19 @@ export function WagmiRequestPermissionsSyncTest() { ) } - return + return } function ConnectedTestContent({ - chain, - provider, + chainId, address }: { - chain: Chain - provider: Provider + chainId: string | number address: Address | undefined }) { const [isRequestPermissionLoading, setRequestPermissionLoading] = useState(false) const { passkey } = usePasskey() - const { grantedPermissions, clearGrantedPermissions, grantPermissions } = useERC7715Permissions() + const { clearSmartSession, setSmartSession, smartSession } = useERC7715Permissions() const toast = useChakraToast() const onRequestPermissions = useCallback(async () => { @@ -65,14 +58,6 @@ function ConnectedTestContent({ if (!passkey) { throw new Error('Passkey not available') } - if (!provider) { - throw new Error('No Provider available, Please connect your wallet.') - } - const walletClient = createWalletClient({ - account: address, - chain, - transport: custom(provider) - }).extend(walletActionsErc7715()) let p256Credential = passkey as P256Credential p256Credential = { ...p256Credential, @@ -83,19 +68,34 @@ function ConnectedTestContent({ } } const passkeyPublicKey = serializePublicKey(p256Credential.publicKey, { to: 'hex' }) - const purchaseDonutPermissions = getPurchaseDonutPermissions() - const response = await grantPermissions(walletClient, { - permissions: purchaseDonutPermissions, - signerKey: { - key: passkeyPublicKey, - type: KeyTypes.secp256r1 - } + const grantPurchaseDonutPermissions: SmartSessionGrantPermissionsRequest = { + // Adding 24 hours to the current time + expiry: Math.floor(Date.now() / 1000) + 24 * 60 * 60, + chainId: toHex(chainId), + address, + signer: { + type: 'keys', + data: { + keys: [ + { + type: 'secp256r1', + publicKey: passkeyPublicKey + } + ] + } + }, + ...purchaseDonutPermissions + } + const response = await grantPermissions(grantPurchaseDonutPermissions) + setSmartSession({ + type: 'sync', + grantedPermissions: response }) toast({ type: 'success', title: 'Permissions Granted', - description: JSON.stringify(response.approvedPermissions, bigIntReplacer) + description: JSON.stringify(response, bigIntReplacer) }) } catch (error) { toast({ @@ -106,22 +106,22 @@ function ConnectedTestContent({ } finally { setRequestPermissionLoading(false) } - }, [passkey, provider, address, chain, grantPermissions, toast]) + }, [passkey, address, chainId, grantPermissions, toast]) return ( diff --git a/apps/laboratory/src/context/ERC7715PermissionsContext.tsx b/apps/laboratory/src/context/ERC7715PermissionsContext.tsx index cde5a545de..8d2253cab1 100644 --- a/apps/laboratory/src/context/ERC7715PermissionsContext.tsx +++ b/apps/laboratory/src/context/ERC7715PermissionsContext.tsx @@ -1,34 +1,37 @@ +/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ import React, { type ReactNode } from 'react' import { createContext } from 'react' -import { - GRANTED_PERMISSIONS_KEY, - removeLocalStorageItem, - WC_COSIGNER_DATA -} from '../utils/LocalStorage' +import { SMART_SESSION_KEY, removeLocalStorageItem } from '../utils/LocalStorage' import { useLocalStorageState } from '../hooks/useLocalStorageState' -import type { GrantPermissionsReturnType } from 'viem/experimental' -import type { AddPermissionResponse } from '../utils/WalletConnectCosignerUtils' +import type { SmartSessionGrantPermissionsResponse } from '@reown/appkit-experimental/smart-session' export type ERC7715PermissionsContextType = { projectId: string - grantedPermissions: GrantPermissionsReturnType | undefined - setGrantedPermissions: React.Dispatch< - React.SetStateAction + smartSession: + | { + grantedPermissions: SmartSessionGrantPermissionsResponse + type: 'async' | 'sync' + } + | undefined + setSmartSession: React.Dispatch< + React.SetStateAction< + | { + grantedPermissions: SmartSessionGrantPermissionsResponse + type: 'async' | 'sync' + } + | undefined + > > - wcCosignerData: AddPermissionResponse | undefined - setWCCosignerData: React.Dispatch> - clearGrantedPermissions: () => void + clearSmartSession: () => void } function noop() { console.warn('WagmiPermissionsAsyncContext used outside of provider') } export const ERC7715PermissionsContext = createContext({ projectId: '', - grantedPermissions: undefined, - wcCosignerData: undefined, - setGrantedPermissions: noop, - setWCCosignerData: noop, - clearGrantedPermissions: noop + clearSmartSession: noop, + smartSession: undefined, + setSmartSession: noop }) interface ERC7715PermissionsProviderProps { @@ -40,27 +43,25 @@ export function ERC7715PermissionsProvider({ children }: ERC7715PermissionsProvi if (!projectId) { throw new Error('NEXT_PUBLIC_PROJECT_ID is not set') } - const [grantedPermissions, setGrantedPermissions] = useLocalStorageState< - GrantPermissionsReturnType | undefined - >(GRANTED_PERMISSIONS_KEY, undefined) - const [wcCosignerData, setWCCosignerData] = useLocalStorageState< - AddPermissionResponse | undefined - >(WC_COSIGNER_DATA, undefined) - - function clearGrantedPermissions() { - removeLocalStorageItem(GRANTED_PERMISSIONS_KEY) - setGrantedPermissions(undefined) + const [smartSession, setSmartSession] = useLocalStorageState< + | { + grantedPermissions: SmartSessionGrantPermissionsResponse + type: 'async' | 'sync' + } + | undefined + >(SMART_SESSION_KEY, undefined) + function clearSmartSession() { + removeLocalStorageItem(SMART_SESSION_KEY) + setSmartSession(undefined) } return ( {children} diff --git a/apps/laboratory/src/hooks/useERC7715Permissions.ts b/apps/laboratory/src/hooks/useERC7715Permissions.ts index d22077280e..f620ca5c2f 100644 --- a/apps/laboratory/src/hooks/useERC7715Permissions.ts +++ b/apps/laboratory/src/hooks/useERC7715Permissions.ts @@ -1,22 +1,5 @@ -import { type Hex, type WalletClient } from 'viem' import { useContext } from 'react' import { ERC7715PermissionsContext } from '../context/ERC7715PermissionsContext' -import { - decodeUncompressedPublicKey, - encodePublicKeyToDID, - hexStringToBase64, - KeyTypes -} from '../utils/EncodingUtils' -import { - type GrantPermissionsParameters, - type GrantPermissionsReturnType, - type WalletActionsErc7715 -} from 'viem/experimental' -import { WalletConnectCosigner } from '../utils/WalletConnectCosignerUtils' - -type RequestPermissionsReturnType = { - approvedPermissions: GrantPermissionsReturnType -} export function useERC7715Permissions() { const context = useContext(ERC7715PermissionsContext) @@ -25,84 +8,11 @@ export function useERC7715Permissions() { throw new Error('useERC7715Permissions must be used within a ERC7715PermissionsProvider') } - const { - grantedPermissions, - wcCosignerData, - setWCCosignerData, - setGrantedPermissions, - clearGrantedPermissions - } = context - - async function grantPermissions( - walletClient: WalletClient & WalletActionsErc7715, - args: { - permissions: GrantPermissionsParameters - signerKey: { - key: Hex - type: KeyTypes - } - } - ): Promise { - const { permissions, signerKey } = args - const accountAddress = walletClient.account?.address - if (!accountAddress) { - throw new Error('Account address not available') - } - const chain = walletClient.chain - if (!chain) { - throw new Error('Chain not available') - } - if (!signerKey) { - throw new Error('Invalid signer data') - } - const dAppKeyDID = encodePublicKeyToDID(signerKey.key, signerKey.type) - - const caip10Address = `eip155:${chain.id}:${accountAddress}` - const walletConnectCosigner = new WalletConnectCosigner() - const addPermissionResponse = await walletConnectCosigner.addPermission(caip10Address, { - permissionType: 'donut-purchase', - data: '', - onChainValidated: false, - required: true - }) - - setWCCosignerData(addPermissionResponse) - const cosignerPublicKey = decodeUncompressedPublicKey(addPermissionResponse.key) - const cosignerKeyDID = encodePublicKeyToDID(cosignerPublicKey, KeyTypes.secp256k1) - permissions.signer = { - type: 'keys', - data: { - ids: [cosignerKeyDID, dAppKeyDID] - } - } - const approvedPermissions = await walletClient.grantPermissions(permissions) - await walletConnectCosigner.updatePermissionsContext(caip10Address, { - pci: addPermissionResponse.pci, - context: { - expiry: approvedPermissions.expiry, - signer: { - type: 'donut-purchase', - data: { - ids: [addPermissionResponse.key, hexStringToBase64(signerKey.key)] - } - }, - signerData: { - userOpBuilder: approvedPermissions.signerData?.userOpBuilder || '' - }, - permissionsContext: approvedPermissions.permissionsContext, - factory: approvedPermissions.factory || '', - factoryData: approvedPermissions.factoryData || '' - } - }) - setGrantedPermissions(approvedPermissions) - - return { approvedPermissions } - } + const { smartSession, setSmartSession, clearSmartSession } = context return { - grantedPermissions, - pci: wcCosignerData?.pci, - clearGrantedPermissions, - grantPermissions + clearSmartSession, + smartSession, + setSmartSession } } diff --git a/apps/laboratory/src/hooks/useWagmiActiveCapabilities.ts b/apps/laboratory/src/hooks/useWagmiActiveCapabilities.ts index 30687945df..78086503d4 100644 --- a/apps/laboratory/src/hooks/useWagmiActiveCapabilities.ts +++ b/apps/laboratory/src/hooks/useWagmiActiveCapabilities.ts @@ -1,4 +1,4 @@ -import { UniversalProvider } from '@walletconnect/universal-provider' +import type UniversalProvider from '@walletconnect/universal-provider' import { useAccount, type Connector } from 'wagmi' import { useState, useEffect, useMemo } from 'react' import { type Address, type WalletCapabilities } from 'viem' @@ -17,7 +17,7 @@ type UseWagmiAvailableCapabilitiesParams = { method: string } -export type Provider = Awaited> | W3mFrameProvider +export type Provider = UniversalProvider | W3mFrameProvider export function useWagmiAvailableCapabilities({ capability, diff --git a/apps/laboratory/src/pages/library/wagmi-permissions-async.tsx b/apps/laboratory/src/pages/library/wagmi-permissions-async.tsx index 8f5d27e1bf..b0526431ac 100644 --- a/apps/laboratory/src/pages/library/wagmi-permissions-async.tsx +++ b/apps/laboratory/src/pages/library/wagmi-permissions-async.tsx @@ -5,14 +5,14 @@ import { AppKitButtons } from '../../components/AppKitButtons' import { ThemeStore } from '../../utils/StoreUtil' import { ConstantsUtil } from '../../utils/ConstantsUtil' import { WagmiPermissionsAsyncTest } from '../../components/Wagmi/WagmiPermissionsAsyncTest' -import { mainnet } from '@reown/appkit/networks' +import { sepolia, baseSepolia, type AppKitNetwork } from '@reown/appkit/networks' import { WagmiAdapter } from '@reown/appkit-adapter-wagmi' import { ERC7715PermissionsProvider } from '../../context/ERC7715PermissionsContext' import { LocalEcdsaKeyProvider } from '../../context/LocalEcdsaKeyContext' const queryClient = new QueryClient() -const networks = ConstantsUtil.EvmNetworks +const networks = [baseSepolia, sepolia] as [AppKitNetwork, ...AppKitNetwork[]] const wagmiAdapter = new WagmiAdapter({ ssr: true, @@ -23,7 +23,7 @@ const wagmiAdapter = new WagmiAdapter({ const modal = createAppKit({ adapters: [wagmiAdapter], networks, - defaultNetwork: mainnet, + defaultNetwork: sepolia, projectId: ConstantsUtil.ProjectId, features: { analytics: true diff --git a/apps/laboratory/src/pages/library/wagmi-permissions-sync.tsx b/apps/laboratory/src/pages/library/wagmi-permissions-sync.tsx index 86a02aa083..edcf6ca932 100644 --- a/apps/laboratory/src/pages/library/wagmi-permissions-sync.tsx +++ b/apps/laboratory/src/pages/library/wagmi-permissions-sync.tsx @@ -5,14 +5,14 @@ import { AppKitButtons } from '../../components/AppKitButtons' import { ThemeStore } from '../../utils/StoreUtil' import { ConstantsUtil } from '../../utils/ConstantsUtil' import { WagmiPermissionsSyncTest } from '../../components/Wagmi/WagmiPermissionsSyncTest' -import { mainnet } from '@reown/appkit/networks' +import { baseSepolia, sepolia, type AppKitNetwork } from '@reown/appkit/networks' import { WagmiAdapter } from '@reown/appkit-adapter-wagmi' import { PasskeyProvider } from '../../context/PasskeyContext' import { ERC7715PermissionsProvider } from '../../context/ERC7715PermissionsContext' const queryClient = new QueryClient() -const networks = ConstantsUtil.EvmNetworks +const networks = [baseSepolia, sepolia] as [AppKitNetwork, ...AppKitNetwork[]] const wagmiAdapter = new WagmiAdapter({ ssr: true, @@ -23,7 +23,7 @@ const wagmiAdapter = new WagmiAdapter({ const modal = createAppKit({ adapters: [wagmiAdapter], networks, - defaultNetwork: mainnet, + defaultNetwork: sepolia, projectId: ConstantsUtil.ProjectId, features: { analytics: true diff --git a/apps/laboratory/src/utils/ConstantsUtil.ts b/apps/laboratory/src/utils/ConstantsUtil.ts index ec0b3e3cea..8cce75f25a 100644 --- a/apps/laboratory/src/utils/ConstantsUtil.ts +++ b/apps/laboratory/src/utils/ConstantsUtil.ts @@ -21,8 +21,7 @@ if (!projectId) { throw new Error('NEXT_PUBLIC_PROJECT_ID is not set') } export const WALLET_URL = process.env['WALLET_URL'] || 'https://react-wallet.walletconnect.com/' -export const WC_COSIGNER_BASE_URL = 'https://rpc.walletconnect.org/v1/sessions' -export const USEROP_BUILDER_SERVICE_BASE_URL = 'https://react-wallet.walletconnect.com/api' +export const USEROP_BUILDER_SERVICE_BASE_URL = 'https://rpc.walletconnect.com/v1/wallet' export const GALLERY_URL = 'https://appkit-gallery.reown.com/' export const DOCS_URL = 'https://docs.reown.com/appkit/overview' diff --git a/apps/laboratory/src/utils/EIP5792Utils.ts b/apps/laboratory/src/utils/EIP5792Utils.ts index 0d2f850791..c11240a6a6 100644 --- a/apps/laboratory/src/utils/EIP5792Utils.ts +++ b/apps/laboratory/src/utils/EIP5792Utils.ts @@ -54,7 +54,6 @@ export function convertCapabilitiesToRecord( Object.entries(accountCapabilities).map(([key, value]) => [parseInt(key, 16), value]) ) } -/* eslint-enable @typescript-eslint/no-explicit-any */ export function getProviderCachedCapabilities( address: string, diff --git a/apps/laboratory/src/utils/ERC7715Utils.ts b/apps/laboratory/src/utils/ERC7715Utils.ts index 1ab32e0c6e..90ff4bb814 100644 --- a/apps/laboratory/src/utils/ERC7715Utils.ts +++ b/apps/laboratory/src/utils/ERC7715Utils.ts @@ -1,11 +1,9 @@ -import type { GrantPermissionsParameters, GrantPermissionsReturnType } from 'viem/experimental' import { abi as donutContractAbi, address as donutContractAddress } from './DonutContract' -import { encodeAbiParameters, hashMessage, parseEther, type Chain } from 'viem' -import { WalletConnectCosigner } from './WalletConnectCosignerUtils' -import { buildUserOp, type Call, type FillUserOpResponse } from './UserOpBuilderServiceUtils' +import { encodeAbiParameters, hashMessage, toHex, type Chain } from 'viem' +import { prepareCalls, sendPreparedCalls, type Call } from './UserOpBuilderServiceUtils' import { signMessage } from 'viem/accounts' -import { bigIntReplacer } from './CommonUtils' import { sign as signWithPasskey } from 'webauthn-p256' +import type { SmartSessionGrantPermissionsRequest } from '@reown/appkit-experimental/smart-session' export type MultikeySigner = { type: 'keys' @@ -14,51 +12,27 @@ export type MultikeySigner = { } } -export function getPurchaseDonutPermissions(): GrantPermissionsParameters { +export function getPurchaseDonutPermissions(): Omit< + SmartSessionGrantPermissionsRequest, + 'signer' | 'chainId' | 'address' | 'expiry' +> { return { - expiry: Date.now() + 24 * 60 * 60, permissions: [ { - type: { - custom: 'donut-purchase' - }, + type: 'contract-call', data: { - target: donutContractAddress, + address: donutContractAddress, abi: donutContractAbi, - valueLimit: parseEther('10').toString(), - functionName: 'function purchase()' - }, - policies: [] + functions: [ + { + functionName: 'purchase' + } + ] + } } - ] - } -} - -async function prepareUserOperationWithPermissions(args: { - actions: Call[] - chain: Chain - permissions: GrantPermissionsReturnType -}): Promise { - const { actions, chain, permissions } = args - if (!permissions) { - throw new Error('No permissions available') - } - const { signerData, permissionsContext } = permissions - - if (!signerData?.userOpBuilder || !signerData.submitToAddress || !permissionsContext) { - throw new Error(`Invalid permissions ${JSON.stringify(permissions, bigIntReplacer)}`) + ], + policies: [] } - - const filledUserOp = await buildUserOp({ - account: signerData.submitToAddress, - chainId: chain.id, - calls: actions, - capabilities: { - permissions: { context: permissionsContext as `0x${string}` } - } - }) - - return filledUserOp } async function signUserOperationWithPasskey(args: { @@ -76,7 +50,6 @@ async function signUserOperationWithPasskey(args: { const authenticatorData = usersPasskeySignature.webauthn.authenticatorData const clientDataJSON = usersPasskeySignature.webauthn.clientDataJSON const responseTypeLocation = usersPasskeySignature.webauthn.typeIndex - // Const userVerificationRequired = usersPasskeySignature.webauthn.userVerificationRequired const r = usersPasskeySignature.signature.r const s = usersPasskeySignature.signature.s @@ -95,116 +68,101 @@ async function signUserOperationWithPasskey(args: { return passkeySignature } -async function signUserOperationWithECDSAKey(args: { - ecdsaPrivateKey: `0x${string}` - userOpHash: `0x${string}` -}): Promise<`0x${string}`> { - const { ecdsaPrivateKey, userOpHash } = args - - const dappSignatureOnUserOp = await signMessage({ - privateKey: ecdsaPrivateKey, - message: { raw: userOpHash } - }) - - return dappSignatureOnUserOp -} - -export async function executeActionsWithPasskeyAndCosignerPermissions(args: { +export async function executeActionsWithPasskey(args: { actions: Call[] passkeyId: string chain: Chain - permissions: GrantPermissionsReturnType - pci: string -}): Promise<`0x${string}`> { - const { actions, passkeyId, chain, permissions, pci } = args - const accountAddress = permissions?.signerData?.submitToAddress + accountAddress: `0x${string}` + permissionsContext: string +}): Promise { + const { actions, passkeyId, chain, accountAddress, permissionsContext } = args + + if (!permissionsContext) { + throw new Error('No permissions available') + } if (!accountAddress) { - throw new Error(`Unable to get account details from granted permission`) + throw new Error('No account Address available') } - if (!pci) { - throw new Error('No WC_COSIGNER PCI data available') - } - const caip10Address = `eip155:${chain?.id}:${accountAddress}` - const filledUserOp = await prepareUserOperationWithPermissions({ - actions, - chain, - permissions + const prepareCallsResponse = await prepareCalls({ + from: accountAddress, + chainId: toHex(chain.id), + calls: actions.map(call => ({ + to: call.to, + data: call.data, + value: toHex(call.value) + })), + capabilities: { + permissions: { context: permissionsContext } + } }) - const userOp = filledUserOp.userOp - const signature = await signUserOperationWithPasskey({ + if (prepareCallsResponse.length !== 1 && prepareCallsResponse[0]) { + throw new Error('Invalid response type') + } + const response = prepareCallsResponse[0] + if (!response || response.preparedCalls.type !== 'user-operation-v07') { + throw new Error('Invalid response type') + } + const signatureRequest = response.signatureRequest + const dappSignature = await signUserOperationWithPasskey({ passkeyId, - userOpHash: filledUserOp.hash + userOpHash: signatureRequest.hash }) - userOp.signature = signature - - const walletConnectCosigner = new WalletConnectCosigner() - const cosignResponse = await walletConnectCosigner.coSignUserOperation(caip10Address, { - pci, - userOp: { - ...userOp, - callData: userOp.callData, - callGasLimit: BigInt(userOp.callGasLimit), - nonce: BigInt(userOp.nonce), - preVerificationGas: BigInt(userOp.preVerificationGas), - verificationGasLimit: BigInt(userOp.verificationGasLimit), - sender: userOp.sender, - signature: userOp.signature, - maxFeePerGas: BigInt(userOp.maxFeePerGas), - maxPriorityFeePerGas: BigInt(userOp.maxPriorityFeePerGas) - } + const sendUserOpResponse = await sendPreparedCalls({ + context: response.context, + preparedCalls: response.preparedCalls, + signature: dappSignature }) - return cosignResponse.receipt as `0x${string}` + return sendUserOpResponse } -export async function executeActionsWithECDSAAndCosignerPermissions(args: { +export async function executeActionsWithECDSAKey(args: { actions: Call[] ecdsaPrivateKey: `0x${string}` chain: Chain - permissions: GrantPermissionsReturnType - pci: string -}): Promise<`0x${string}`> { - const { ecdsaPrivateKey, actions, chain, permissions, pci } = args - const accountAddress = permissions?.signerData?.submitToAddress + accountAddress: `0x${string}` + permissionsContext: string +}): Promise { + const { ecdsaPrivateKey, actions, chain, accountAddress, permissionsContext } = args + if (!permissionsContext) { + throw new Error('No permissions available') + } if (!accountAddress) { - throw new Error(`Unable to get account details from granted permission`) + throw new Error('No account Address available') } - if (!pci) { - throw new Error('No WC_COSIGNER PCI data available') - } - const caip10Address = `eip155:${chain?.id}:${accountAddress}` - const filledUserOp = await prepareUserOperationWithPermissions({ - actions, - chain, - permissions + const prepareCallsResponse = await prepareCalls({ + from: accountAddress, + chainId: toHex(chain.id), + calls: actions.map(call => ({ + to: call.to, + data: call.data, + value: toHex(call.value) + })), + capabilities: { + permissions: { context: permissionsContext } + } }) - const userOp = filledUserOp.userOp - - const dappSignature = await signUserOperationWithECDSAKey({ - ecdsaPrivateKey, - userOpHash: filledUserOp.hash + if (prepareCallsResponse.length !== 1 && prepareCallsResponse[0]) { + throw new Error('Invalid response type') + } + const response = prepareCallsResponse[0] + if (!response || response.preparedCalls.type !== 'user-operation-v07') { + throw new Error('Invalid response type') + } + const signatureRequest = response.signatureRequest + const dappSignature = await signMessage({ + privateKey: ecdsaPrivateKey, + message: { raw: signatureRequest.hash } }) - userOp.signature = dappSignature - const walletConnectCosigner = new WalletConnectCosigner() - const cosignResponse = await walletConnectCosigner.coSignUserOperation(caip10Address, { - pci, - userOp: { - ...userOp, - callData: userOp.callData, - callGasLimit: BigInt(userOp.callGasLimit), - nonce: BigInt(userOp.nonce), - preVerificationGas: BigInt(userOp.preVerificationGas), - verificationGasLimit: BigInt(userOp.verificationGasLimit), - sender: userOp.sender, - signature: userOp.signature, - maxFeePerGas: BigInt(userOp.maxFeePerGas), - maxPriorityFeePerGas: BigInt(userOp.maxPriorityFeePerGas) - } + const sendUserOpResponse = await sendPreparedCalls({ + context: response.context, + preparedCalls: response.preparedCalls, + signature: dappSignature }) - return cosignResponse.receipt as `0x${string}` + return sendUserOpResponse } diff --git a/apps/laboratory/src/utils/LocalStorage.ts b/apps/laboratory/src/utils/LocalStorage.ts index 7e529cca9b..a6700e8e36 100644 --- a/apps/laboratory/src/utils/LocalStorage.ts +++ b/apps/laboratory/src/utils/LocalStorage.ts @@ -2,9 +2,8 @@ import { bigIntReplacer } from './CommonUtils' export const PASSKEY_LOCALSTORAGE_KEY = 'passkeyId' export const LAST_USED_ADDRESS_KEY = 'lastUsedAddress' -export const LOCAL_SIGNER_KEY = 'localECDSASigner' -export const GRANTED_PERMISSIONS_KEY = 'grantedPermissions' -export const WC_COSIGNER_DATA = 'wc_cosignerData' +export const LOCAL_SIGNER_KEY = 'ecdsa-key' +export const SMART_SESSION_KEY = 'smart-session' /** * Sets an item in the local storage. * @param key - The key to set the item with. diff --git a/apps/laboratory/src/utils/UserOpBuilderServiceUtils.ts b/apps/laboratory/src/utils/UserOpBuilderServiceUtils.ts index fb0f4933f1..fc41910206 100644 --- a/apps/laboratory/src/utils/UserOpBuilderServiceUtils.ts +++ b/apps/laboratory/src/utils/UserOpBuilderServiceUtils.ts @@ -1,19 +1,29 @@ -import axios, { AxiosError } from 'axios' import { bigIntReplacer } from '../utils/CommonUtils' import type { Address, Hex } from 'viem' import { USEROP_BUILDER_SERVICE_BASE_URL } from './ConstantsUtil' export type Call = { to: Address; value: bigint; data: Hex } -export type BuildUserOpRequestArguments = { - account: Address - chainId: number - calls: Call[] - capabilities: { - paymasterService?: { url: string } - permissions?: { context: Hex } - } +export type UserOperationWithBigIntAsHex = { + sender: Address + nonce: Hex + factory: Address | undefined + factoryData: Hex | undefined + callData: Hex + callGasLimit: Hex + verificationGasLimit: Hex + preVerificationGas: Hex + maxFeePerGas: Hex + maxPriorityFeePerGas: Hex + paymaster: Address | undefined + paymasterVerificationGasLimit: Hex | undefined + paymasterPostOpGasLimit: Hex | undefined + paymasterData: Hex | undefined + signature: Hex + initCode?: never + paymasterAndData?: never } + /** * UserOperation v0.7 */ @@ -37,26 +47,46 @@ export type UserOperation = { paymasterAndData?: never } -export type FillUserOpResponse = { - userOp: UserOperation - hash: Hex -} - export type ErrorResponse = { message: string error: string } -export type SendUserOpWithSignatureParams = { - chainId: number - userOp: UserOperation - signature: Hex - permissionsContext?: Hex +export type PrepareCallsParams = { + from: `0x${string}` + chainId: `0x${string}` + calls: { + to: `0x${string}` + data: `0x${string}` + value: `0x${string}` + }[] + capabilities: Record } -export type SendUserOpWithSignatureResponse = { - receipt: Hex + +export type PrepareCallsReturnValue = { + preparedCalls: { + type: string + data: unknown + chainId: `0x${string}` + } + signatureRequest: { + hash: `0x${string}` + } + context: string } +export type SendPreparedCallsParams = { + preparedCalls: { + type: string + data: unknown + chainId: `0x${string}` + } + signature: `0x${string}` + context: string +} + +export type SendPreparedCallsReturnValue = string + // Define a custom error type export class UserOpBuilderApiError extends Error { constructor( @@ -68,74 +98,68 @@ export class UserOpBuilderApiError extends Error { } } -// Function to send requests to the CoSigner API -async function sendUserOpBuilderRequest< - TRequest, - TResponse extends object, - TQueryParams extends Record = Record ->(args: { +async function jsonRpcRequest( + method: string, + params: TParams, url: string - headers: Record - data: TRequest - queryParams?: TQueryParams - transformRequest?: (data: TRequest) => unknown -}): Promise { - const { url, data, queryParams, headers, transformRequest } = args - const transformedData = transformRequest ? transformRequest(data) : data - - try { - const response = await axios.post(url, transformedData, { - params: queryParams, - headers - }) - - if ('error' in response.data) { - throw new Error(response.data.error) - } - - return response.data - } catch (error) { - if (axios.isAxiosError(error)) { - const axiosError = error as AxiosError - if (axiosError.response) { - throw new UserOpBuilderApiError( - axiosError.response.status, - JSON.stringify(axiosError.response.data) - ) - } else { - throw new UserOpBuilderApiError(500, 'Network error') - } - } - // Re-throw if it's not an Axios error - throw error - } -} - -export async function buildUserOp(args: BuildUserOpRequestArguments): Promise { - const response = await sendUserOpBuilderRequest({ - url: `${USEROP_BUILDER_SERVICE_BASE_URL}/build`, - data: args, +): Promise { + const response = await fetch(url, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, - transformRequest: data => JSON.stringify(data, bigIntReplacer) + body: JSON.stringify( + { + jsonrpc: '2.0', + id: '1', + method, + params + }, + bigIntReplacer + ) }) - return response + if (!response.ok) { + throw new UserOpBuilderApiError(response.status, await response.text()) + } + + const data = await response.json() + + if ('error' in data) { + throw new UserOpBuilderApiError(500, JSON.stringify(data.error)) + } + + return data.result } -export async function sendUserOp(args: SendUserOpWithSignatureParams) { - const response = await sendUserOpBuilderRequest< - SendUserOpWithSignatureParams, - SendUserOpWithSignatureResponse - >({ - url: `${USEROP_BUILDER_SERVICE_BASE_URL}/sendUserOp`, - data: args, - headers: { - 'Content-Type': 'application/json' - }, - transformRequest: data => JSON.stringify(data, bigIntReplacer) - }) +export async function prepareCalls(args: PrepareCallsParams): Promise { + const projectId = process.env['NEXT_PUBLIC_PROJECT_ID'] + if (!projectId) { + throw new Error('NEXT_PUBLIC_PROJECT_ID is not set') + } + + const url = `${USEROP_BUILDER_SERVICE_BASE_URL}?projectId=${projectId}` + + return jsonRpcRequest( + 'wallet_prepareCalls', + [args], + url + ) +} + +export async function sendPreparedCalls( + args: SendPreparedCallsParams +): Promise { + const projectId = process.env['NEXT_PUBLIC_PROJECT_ID'] + if (!projectId) { + throw new Error('NEXT_PUBLIC_PROJECT_ID is not set') + } + + const url = `${USEROP_BUILDER_SERVICE_BASE_URL}?projectId=${projectId}` - return response + return jsonRpcRequest( + 'wallet_sendPreparedCalls', + [args], + url + ) } diff --git a/apps/laboratory/src/utils/WalletConnectCosignerUtils.ts b/apps/laboratory/src/utils/WalletConnectCosignerUtils.ts deleted file mode 100644 index f244534fb3..0000000000 --- a/apps/laboratory/src/utils/WalletConnectCosignerUtils.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* eslint-disable max-classes-per-file */ -import axios, { AxiosError } from 'axios' -import { bigIntReplacer } from './CommonUtils' -import type { UserOperation } from './UserOpBuilderServiceUtils' -import { WC_COSIGNER_BASE_URL } from './ConstantsUtil' - -// Define types for the request and response -type AddPermission = { - permissionType: string - data: string - required: boolean - onChainValidated: boolean -} - -type AddPermissionRequest = { - permission: AddPermission -} - -export type AddPermissionResponse = { - pci: string - key: string -} - -type Signer = { - type: string - data: { - ids: string[] - } -} - -type SignerData = { - userOpBuilder: string -} - -type PermissionsContext = { - signer: Signer - expiry: number - signerData: SignerData - factory?: string - factoryData?: string - permissionsContext: string -} - -type UpdatePermissionsContextRequest = { - pci: string - signature?: string - context: PermissionsContext -} - -type RevokePermissionRequest = { - pci: string - signature: string -} - -type CoSignRequest = { - pci: string - userOp: UserOperation -} - -type CoSignResponse = { - receipt: string -} - -// Define a custom error type -export class CoSignerApiError extends Error { - constructor( - public status: number, - message: string - ) { - super(message) - this.name = 'CoSignerApiError' - } -} - -// Function to send requests to the CoSigner API -async function sendCoSignerRequest< - TRequest, - TResponse, - TQueryParams extends Record = Record ->(args: { - url: string - data: TRequest - queryParams?: TQueryParams - headers: Record - transformRequest?: (data: TRequest) => unknown -}): Promise { - const { url, data, queryParams = {}, headers, transformRequest } = args - const transformedData = transformRequest ? transformRequest(data) : data - - try { - const response = await axios.post(url, transformedData, { - params: queryParams, - headers - }) - - return response.data - } catch (error) { - if (axios.isAxiosError(error)) { - const axiosError = error as AxiosError - if (axiosError.response) { - throw new CoSignerApiError( - axiosError.response.status, - JSON.stringify(axiosError.response.data) - ) - } else { - throw new CoSignerApiError(500, 'Network error') - } - } - // Re-throw if it's not an Axios error - throw error - } -} - -// Class to interact with the WalletConnect CoSigner API -export class WalletConnectCosigner { - private baseUrl: string - private projectId: string - - constructor() { - this.baseUrl = WC_COSIGNER_BASE_URL - const projectId = process.env['NEXT_PUBLIC_PROJECT_ID'] - if (!projectId) { - throw new Error('NEXT_PUBLIC_PROJECT_ID is not set') - } - this.projectId = projectId - } - - async addPermission(address: string, permission: AddPermission): Promise { - const url = `${this.baseUrl}/${encodeURIComponent(address)}` - - return await sendCoSignerRequest< - AddPermissionRequest, - AddPermissionResponse, - { projectId: string } - >({ - url, - data: { permission }, - queryParams: { projectId: this.projectId }, - headers: { 'Content-Type': 'application/json' } - }) - } - - async updatePermissionsContext( - address: string, - updateData: UpdatePermissionsContextRequest - ): Promise { - const url = `${this.baseUrl}/${encodeURIComponent(address)}/context` - await sendCoSignerRequest({ - url, - data: updateData, - queryParams: { projectId: this.projectId }, - headers: { 'Content-Type': 'application/json' } - }) - } - - async revokePermission(address: string, revokeData: RevokePermissionRequest): Promise { - const url = `${this.baseUrl}/${encodeURIComponent(address)}/revoke` - await sendCoSignerRequest({ - url, - data: revokeData, - queryParams: { projectId: this.projectId }, - headers: { 'Content-Type': 'application/json' } - }) - } - - async coSignUserOperation(address: string, coSignData: CoSignRequest): Promise { - const url = `${this.baseUrl}/${encodeURIComponent(address)}/sign` - - return await sendCoSignerRequest({ - url, - data: coSignData, - queryParams: { projectId: this.projectId }, - headers: { 'Content-Type': 'application/json' }, - transformRequest: (value: CoSignRequest) => JSON.stringify(value, bigIntReplacer) - }) - } -} diff --git a/packages/adapters/ethers/src/client.ts b/packages/adapters/ethers/src/client.ts index afa0f4aae1..016e9ffded 100644 --- a/packages/adapters/ethers/src/client.ts +++ b/packages/adapters/ethers/src/client.ts @@ -409,6 +409,35 @@ export class EthersAdapter { return await EthersMethods.estimateGas(data, provider, address, Number(caipNetwork?.id)) }, + getCapabilities: async (params: string) => { + const provider = ProviderUtil.getProvider(CommonConstantsUtil.CHAIN.EVM) + + if (!provider) { + throw new Error('Provider is undefined') + } + + const walletCapabilitiesString = provider.session?.sessionProperties?.['capabilities'] + if (walletCapabilitiesString) { + const walletCapabilities = EthersMethods.parseWalletCapabilities(walletCapabilitiesString) + const accountCapabilities = walletCapabilities[params] + if (accountCapabilities) { + return accountCapabilities + } + } + + return await provider.request({ method: 'wallet_getCapabilities', params: [params] }) + }, + + grantPermissions: async params => { + const provider = ProviderUtil.getProvider(CommonConstantsUtil.CHAIN.EVM) + + if (!provider) { + throw new Error('Provider is undefined') + } + + return await provider.request({ method: 'wallet_grantPermissions', params }) + }, + sendTransaction: async data => { if (data.chainNamespace && data.chainNamespace !== 'eip155') { throw new Error(`Invalid chain namespace - Expected eip155, got ${data.chainNamespace}`) diff --git a/packages/adapters/ethers/src/utils/EthersMethods.ts b/packages/adapters/ethers/src/utils/EthersMethods.ts index ce1b1c5763..87aebcf707 100644 --- a/packages/adapters/ethers/src/utils/EthersMethods.ts +++ b/packages/adapters/ethers/src/utils/EthersMethods.ts @@ -150,6 +150,14 @@ export const EthersMethods = { return false }, + parseWalletCapabilities: (str: string) => { + try { + return JSON.parse(str) + } catch (error) { + throw new Error('Error parsing wallet capabilities') + } + }, + parseUnits, formatUnits } diff --git a/packages/adapters/solana/src/client.ts b/packages/adapters/solana/src/client.ts index 849bdfe35d..d3e4405aac 100644 --- a/packages/adapters/solana/src/client.ts +++ b/packages/adapters/solana/src/client.ts @@ -237,6 +237,10 @@ export class SolanaAdapter implements ChainAdapter { writeContract: async () => await Promise.resolve('0x'), + getCapabilities: async () => await Promise.resolve('0x'), + + grantPermissions: async () => await Promise.resolve('0x'), + sendTransaction: async params => { if (params.chainNamespace !== CommonConstantsUtil.CHAIN.SOLANA) { throw new Error('Chain namespace is not supported') diff --git a/packages/adapters/wagmi/src/client.ts b/packages/adapters/wagmi/src/client.ts index c40f6ef8ba..9e7c5b774c 100644 --- a/packages/adapters/wagmi/src/client.ts +++ b/packages/adapters/wagmi/src/client.ts @@ -61,6 +61,7 @@ import { getEmailCaipNetworks, getTransport, getWalletConnectCaipNetworks, + parseWalletCapabilities, requireCaipAddress } from './utils/helpers.js' import { W3mFrameHelpers, W3mFrameRpcConstants } from '@reown/appkit-wallet' @@ -107,7 +108,11 @@ const OPTIONAL_METHODS = [ 'wallet_requestPermissions', 'wallet_registerOnboarding', 'wallet_watchAsset', - 'wallet_scanQRCode' + 'wallet_scanQRCode', + 'wallet_getCallsStatus', + 'wallet_sendCalls', + 'wallet_getCapabilities', + 'wallet_grantPermissions' ] // @ts-expect-error: Overridden state type is correct @@ -430,6 +435,58 @@ export class WagmiAdapter implements ChainAdapter { return BigInt(0) } }, + + getCapabilities: async (params: string) => { + if (!this.wagmiConfig) { + throw new Error('connectionControllerClient:getCapabilities - wagmiConfig is undefined') + } + + const connections = getConnections(this.wagmiConfig) + const connection = connections[0] + + if (!connection?.connector) { + throw new Error('connectionControllerClient:getCapabilities - connector is undefined') + } + + const provider = (await connection.connector.getProvider()) as UniversalProvider + + if (!provider) { + throw new Error('connectionControllerClient:getCapabilities - provider is undefined') + } + + const walletCapabilitiesString = provider.session?.sessionProperties?.['capabilities'] + if (walletCapabilitiesString) { + const walletCapabilities = parseWalletCapabilities(walletCapabilitiesString) + const accountCapabilities = walletCapabilities[params] + if (accountCapabilities) { + return accountCapabilities + } + } + + return await provider.request({ method: 'wallet_getCapabilities', params: [params] }) + }, + + grantPermissions: async params => { + if (!this.wagmiConfig) { + throw new Error('connectionControllerClient:grantPermissions - wagmiConfig is undefined') + } + + const connections = getConnections(this.wagmiConfig) + const connection = connections[0] + + if (!connection?.connector) { + throw new Error('connectionControllerClient:grantPermissions - connector is undefined') + } + + const provider = (await connection.connector.getProvider()) as UniversalProvider + + if (!provider) { + throw new Error('connectionControllerClient:grantPermissions - provider is undefined') + } + + return provider.request({ method: 'wallet_grantPermissions', params }) + }, + sendTransaction: async (data: SendTransactionArgs) => { if (data.chainNamespace && data.chainNamespace !== 'eip155') { throw new Error(`Invalid chain namespace - Expected eip155, got ${data.chainNamespace}`) diff --git a/packages/adapters/wagmi/src/utils/helpers.ts b/packages/adapters/wagmi/src/utils/helpers.ts index 59b7657795..1e4e679cb1 100644 --- a/packages/adapters/wagmi/src/utils/helpers.ts +++ b/packages/adapters/wagmi/src/utils/helpers.ts @@ -70,3 +70,11 @@ export function requireCaipAddress(caipAddress: string) { return account } + +export function parseWalletCapabilities(str: string) { + try { + return JSON.parse(str) + } catch (error) { + throw new Error('Error parsing wallet capabilities') + } +} diff --git a/packages/appkit/src/tests/mocks/UniversalProvider.ts b/packages/appkit/src/tests/mocks/UniversalProvider.ts index a2e81bb321..e3eefa9842 100644 --- a/packages/appkit/src/tests/mocks/UniversalProvider.ts +++ b/packages/appkit/src/tests/mocks/UniversalProvider.ts @@ -24,6 +24,7 @@ export const mockProvider = { 'wallet_sendCalls', 'wallet_showCallsStatus', 'wallet_getCallsStatus', + 'wallet_grantPermissions', 'wallet_switchEthereumChain' ], rpcMap: { diff --git a/packages/appkit/src/tests/utils/HelpersUtil.test.ts b/packages/appkit/src/tests/utils/HelpersUtil.test.ts index 2683627bf8..6aab3feeec 100644 --- a/packages/appkit/src/tests/utils/HelpersUtil.test.ts +++ b/packages/appkit/src/tests/utils/HelpersUtil.test.ts @@ -83,6 +83,7 @@ describe('WcHelpersUtil', () => { 'wallet_sendCalls', 'wallet_showCallsStatus', 'wallet_getCallsStatus', + 'wallet_grantPermissions', 'wallet_switchEthereumChain' ]) }) diff --git a/packages/appkit/src/universal-adapter/client.ts b/packages/appkit/src/universal-adapter/client.ts index b6086e1fa3..1d2d02a6da 100644 --- a/packages/appkit/src/universal-adapter/client.ts +++ b/packages/appkit/src/universal-adapter/client.ts @@ -49,7 +49,13 @@ const OPTIONAL_METHODS = [ 'wallet_requestPermissions', 'wallet_registerOnboarding', 'wallet_watchAsset', - 'wallet_scanQRCode' + 'wallet_scanQRCode', + // EIP-5792 + 'wallet_getCallsStatus', + 'wallet_sendCalls', + 'wallet_getCapabilities', + // EIP-7715 + 'wallet_grantPermissions' ] // -- Client -------------------------------------------------------------------- @@ -256,6 +262,34 @@ export class UniversalAdapterClient { writeContract: async () => await Promise.resolve('0x'), + getCapabilities: async (params: string) => { + const provider = await this.getWalletConnectProvider() + + if (!provider) { + throw new Error('connectionControllerClient:getCapabilities - provider is undefined') + } + + const walletCapabilitiesString = provider.session?.sessionProperties?.['capabilities'] + if (walletCapabilitiesString) { + const walletCapabilities = this.parseWalletCapabilities(walletCapabilitiesString) + const accountCapabilities = walletCapabilities[params] + if (accountCapabilities) { + return accountCapabilities + } + } + + return await provider.request({ method: 'wallet_getCapabilities', params: [params] }) + }, + + grantPermissions: async (params: object | readonly unknown[]) => { + const provider = await this.getWalletConnectProvider() + if (!provider) { + throw new Error('connectionControllerClient:grantPermissions - provider is undefined') + } + + return provider.request({ method: 'wallet_grantPermissions', params }) + }, + sendTransaction: async () => await Promise.resolve('0x'), parseUnits: () => BigInt(0), @@ -611,4 +645,13 @@ export class UniversalAdapterClient { this.appKit?.setConnectors(w3mConnectors) } + private parseWalletCapabilities(walletCapabilitiesString: string) { + try { + const walletCapabilities = JSON.parse(walletCapabilitiesString) + + return walletCapabilities + } catch (error) { + throw new Error('Error parsing wallet capabilities') + } + } } diff --git a/packages/appkit/src/utils/HelpersUtil.ts b/packages/appkit/src/utils/HelpersUtil.ts index 10da34d9cd..6b1594ee67 100644 --- a/packages/appkit/src/utils/HelpersUtil.ts +++ b/packages/appkit/src/utils/HelpersUtil.ts @@ -28,6 +28,7 @@ export const WcHelpersUtil = { 'wallet_sendCalls', 'wallet_showCallsStatus', 'wallet_getCallsStatus', + 'wallet_grantPermissions', 'wallet_switchEthereumChain' ] default: diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index 6251c86d33..8c0098d622 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -39,6 +39,8 @@ export interface ConnectionControllerClient { writeContract: (args: WriteContractArgs) => Promise<`0x${string}` | null> getEnsAddress: (value: string) => Promise getEnsAvatar: (value: string) => Promise + grantPermissions: (params: readonly unknown[] | object) => Promise + getCapabilities: (params: string) => Promise } export interface ConnectionControllerState { @@ -177,6 +179,14 @@ export const ConnectionController = { return this._getClient().sendTransaction(args) }, + async getCapabilities(params: string) { + return this._getClient().getCapabilities(params) + }, + + async grantPermissions(params: object | readonly unknown[]) { + return this._getClient().grantPermissions(params) + }, + async estimateGas(args: EstimateGasTransactionArgs) { return this._getClient().estimateGas(args) }, diff --git a/packages/core/tests/controllers/ChainController.test.ts b/packages/core/tests/controllers/ChainController.test.ts index 8a3d8dfd48..1a38f98062 100644 --- a/packages/core/tests/controllers/ChainController.test.ts +++ b/packages/core/tests/controllers/ChainController.test.ts @@ -98,7 +98,9 @@ const connectionControllerClient: ConnectionControllerClient = { sendTransaction: () => Promise.resolve('0x'), writeContract: () => Promise.resolve('0x'), getEnsAddress: async (value: string) => Promise.resolve(value), - getEnsAvatar: async (value: string) => Promise.resolve(value) + getEnsAvatar: async (value: string) => Promise.resolve(value), + getCapabilities: async () => Promise.resolve(''), + grantPermissions: async () => Promise.resolve('') } const networkControllerClient: NetworkControllerClient = { diff --git a/packages/core/tests/controllers/ConnectionController.test.ts b/packages/core/tests/controllers/ConnectionController.test.ts index 57164a8fad..3f08c75ba0 100644 --- a/packages/core/tests/controllers/ConnectionController.test.ts +++ b/packages/core/tests/controllers/ConnectionController.test.ts @@ -34,7 +34,9 @@ const client: ConnectionControllerClient = { sendTransaction: () => Promise.resolve('0x'), writeContract: () => Promise.resolve('0x'), getEnsAddress: async (value: string) => Promise.resolve(value), - getEnsAvatar: async (value: string) => Promise.resolve(value) + getEnsAvatar: async (value: string) => Promise.resolve(value), + getCapabilities: async () => Promise.resolve(''), + grantPermissions: async () => Promise.resolve('0x') } const clientConnectWalletConnectSpy = vi.spyOn(client, 'connectWalletConnect') @@ -51,7 +53,9 @@ const partialClient: ConnectionControllerClient = { sendTransaction: () => Promise.resolve('0x'), writeContract: () => Promise.resolve('0x'), getEnsAddress: async (value: string) => Promise.resolve(value), - getEnsAvatar: async (value: string) => Promise.resolve(value) + getEnsAvatar: async (value: string) => Promise.resolve(value), + getCapabilities: async () => Promise.resolve(''), + grantPermissions: async () => Promise.resolve('0x') } const evmAdapter = { diff --git a/packages/experimental/.eslintrc.json b/packages/experimental/.eslintrc.json new file mode 100644 index 0000000000..0f959f167e --- /dev/null +++ b/packages/experimental/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": ["plugin:require-extensions/recommended", "../../.eslintrc.json"], + "plugins": ["require-extensions"] +} diff --git a/packages/experimental/.npmignore b/packages/experimental/.npmignore new file mode 100644 index 0000000000..8d672eb83c --- /dev/null +++ b/packages/experimental/.npmignore @@ -0,0 +1,11 @@ +*.log +*.env +.eslintrc.json +.turbo +tests +src +exports +node_modules +package-lock.json +tsconfig.json +index.ts diff --git a/packages/experimental/CHANGELOG.md b/packages/experimental/CHANGELOG.md new file mode 100644 index 0000000000..cb4cd778c7 --- /dev/null +++ b/packages/experimental/CHANGELOG.md @@ -0,0 +1 @@ +# @reown/appkit-experimental diff --git a/packages/experimental/exports/index.ts b/packages/experimental/exports/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/experimental/exports/smart-session/index.ts b/packages/experimental/exports/smart-session/index.ts new file mode 100644 index 0000000000..f48b6146b2 --- /dev/null +++ b/packages/experimental/exports/smart-session/index.ts @@ -0,0 +1,10 @@ +export { + grantPermissions, + isSmartSessionSupported +} from '../../src/smart-session/grantPermissions.js' + +export type { + KeyType, + SmartSessionGrantPermissionsRequest, + SmartSessionGrantPermissionsResponse +} from '../../src/smart-session/utils/TypeUtils.js' diff --git a/packages/experimental/package.json b/packages/experimental/package.json new file mode 100644 index 0000000000..3f940c3218 --- /dev/null +++ b/packages/experimental/package.json @@ -0,0 +1,70 @@ +{ + "name": "@reown/appkit-experimental", + "version": "1.0.0", + "type": "module", + "main": "./dist/esm/exports/index.js", + "types": "./dist/types/exports/index.d.ts", + "files": [ + "dist", + "!tsconfig.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/types/exports/index.d.ts", + "import": "./dist/esm/exports/index.js", + "default": "./dist/esm/exports/index.js" + }, + "./smart-session": { + "types": "./dist/types/exports/smart-session/index.d.ts", + "import": "./dist/esm/exports/smart-session/index.js", + "default": "./dist/esm/exports/smart-session/index.js" + } + }, + "typesVersions": { + "*": { + "smart-session": [ + "./dist/types/exports/smart-session/index.d.ts" + ] + } + }, + "scripts": { + "build:clean": "rm -rf dist", + "build": "tsc --build", + "watch": "tsc --watch", + "typecheck": "tsc --noEmit", + "test": "vitest run --dir tests --coverage.enabled=true --coverage.reporter=json --coverage.reporter=json-summary --coverage.reportOnFailure=true", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx" + }, + "dependencies": { + "@reown/appkit": "workspace:*", + "@reown/appkit-core": "workspace:*", + "@reown/appkit-common": "workspace:*", + "axios": "1.7.2", + "zod": "3.22.4" + }, + "devDependencies": { + "@types/axios-mock-adapter": "1.10.0", + "@vitest/coverage-v8": "2.0.5", + "axios-mock-adapter": "2.0.0", + "vitest": "2.0.5" + }, + "keywords": [ + "web3", + "crypto", + "ethereum", + "appkit", + "reown", + "lit", + "experimental" + ], + "author": "Reown (https://reown.com)", + "license": "Apache-2.0", + "homepage": "https://github.com/reown-com/appkit", + "repository": { + "type": "git", + "url": "git+https://github.com/reown-com/appkit.git" + }, + "bugs": { + "url": "https://github.com/reown-com/appkit/issues" + } +} diff --git a/packages/experimental/readme.md b/packages/experimental/readme.md new file mode 100644 index 0000000000..2f194961a5 --- /dev/null +++ b/packages/experimental/readme.md @@ -0,0 +1,23 @@ +#### 📚 [Documentation](https://docs.reown.com/2.0/appkit/about) + +#### 🔗 [Website](https://reown.com/appkit) + +# AppKit Experimental (@reown/appkit-experimental) + +The @reown/appkit-experimental package provides a set of cutting-edge, experimental features for Web3 developers. This package serves as a testing ground for innovative concepts and implementations that are not yet widely adopted or standardized in the blockchain ecosystem. + +## Key Characteristics + +- **Experimental Nature**: Features in this package are considered experimental and may undergo significant changes. +- **EIP Implementation**: Includes implementations of non-finalized Ethereum Improvement Proposals (EIPs). +- **Ecosystem Exploration**: Offers functionalities that are not yet widely adopted in the broader Web3 ecosystem. + +## Installation + +```bash +npm install @reown/appkit-experimental +``` + +## Smart Session + +The `@reown/appkit-experimental/smart-session` allows developers to easily integrate session-based permission handling within their dApps. Using the `grantPermissions` method, dApps can send permission requests to connected wallets. diff --git a/packages/experimental/src/smart-session/grantPermissions.ts b/packages/experimental/src/smart-session/grantPermissions.ts new file mode 100644 index 0000000000..d455309259 --- /dev/null +++ b/packages/experimental/src/smart-session/grantPermissions.ts @@ -0,0 +1,144 @@ +import { ChainController, ConnectionController, OptionsController } from '@reown/appkit-core' +import { ProviderUtil } from '@reown/appkit/store' +import { ConstantsUtil as CommonConstantsUtil } from '@reown/appkit-common' +import type { + SmartSessionGrantPermissionsRequest, + SmartSessionGrantPermissionsResponse +} from './utils/TypeUtils.js' +import { + assertWalletGrantPermissionsResponse, + extractChainAndAddress, + updateRequestSigner, + validateRequest +} from './helper/index.js' +import { CosignerService } from './utils/CosignerService.js' +import { ERROR_MESSAGES } from './schema/index.js' +import type { PermissionsCapability, WalletCapabilities } from './utils/ERC5792Types.js' +import { ERC7715_METHOD } from './utils/ConstantUtils.js' + +/** + * 1. Validate the request using the SmartSessionGrantPermissionsRequestSchema + * 2. check connected wallet supports `wallet_grantPermissions` method + * 3. check connected wallet supports permissions capabilities + * 3.1. if supported then validate request against the supported permissions capabilities of the connected wallet + * 3.2. if validation fails throw error + * 4. Add permission using CosignerService + * 5. Update request signer with the cosigner key + * 6. Call `wallet_grantPermissions` method + * 7. Validate and type guard the response + * 8. Activate the permissions using CosignerService + * 9. Return the permissions granted and the context + * @param request SmartSessionGrantPermissionsRequest + * @returns SmartSessionGrantPermissionsResponse + * + */ +export async function grantPermissions( + request: SmartSessionGrantPermissionsRequest +): Promise { + validateRequest(request) + + const isSupported = isSmartSessionSupported() + if (!isSupported) { + throw new Error('Wallet does not support `wallet_grantPermissions` method') + } + + const { activeCaipAddress } = ChainController.state + + // Ensure the namespace is supported and extract address + const chainAndAddress = extractChainAndAddress(activeCaipAddress) + if (!activeCaipAddress || !chainAndAddress) { + throw new Error(ERROR_MESSAGES.INVALID_ADDRESS) + } + // Fetch the ConnectionController client + const connectionControllerClient = ConnectionController._getClient(CommonConstantsUtil.CHAIN.EVM) + + //Check for connected wallet supports permissions capabilities + const walletCapabilities = (await connectionControllerClient.getCapabilities( + chainAndAddress.address + )) as WalletCapabilities + + const hexChainId: `0x${string}` = `0x${parseInt(chainAndAddress.chain, 10).toString(16)}` + const permissionsCapabilities = walletCapabilities?.[hexChainId]?.permissions + + if (permissionsCapabilities) { + validateRequestForSupportedPermissionsCapability(request, permissionsCapabilities) + } + + // Retrieve state values + const { projectId } = OptionsController.state + + // Instantiate CosignerService and process permissions + const cosignerService = new CosignerService(projectId) + const addPermissionResponse = await cosignerService.addPermission(activeCaipAddress, request) + + // Update request signer with the cosigner key + updateRequestSigner(request, addPermissionResponse.key) + + const rawResponse = await connectionControllerClient.grantPermissions([request]) + + // Validate and type guard the response + const response = assertWalletGrantPermissionsResponse(rawResponse) + + // Activate the permissions using CosignerService + await cosignerService.activatePermissions(activeCaipAddress, { + pci: addPermissionResponse.pci, + ...response + }) + + // Return the permissions granted and the context + return { + permissions: response.permissions, + context: addPermissionResponse.pci, + expiry: response.expiry, + address: response.address || chainAndAddress.address, + chainId: response.chainId + } +} + +export function validateRequestForSupportedPermissionsCapability( + request: SmartSessionGrantPermissionsRequest, + capabilities: PermissionsCapability +) { + if (!capabilities.supported) { + throw new Error('Wallet does not support permissions capabilities') + } + + const supportedPermissions = capabilities.permissionTypes + const supportedSigners = capabilities.signerTypes + const supportedPolicies = capabilities.policyTypes + + request.permissions.forEach(permission => { + if (!supportedPermissions.includes(permission.type)) { + throw new Error(`Permission type ${permission.type} is not supported by the connected wallet`) + } + }) + + if (!supportedSigners.includes(request.signer.type)) { + throw new Error(`Signer type ${request.signer.type} is not supported by the connected wallet`) + } + + request.policies.forEach(policy => { + if (!supportedPolicies.includes(policy.type)) { + throw new Error(`Policy type ${policy.type} is not supported by the connected wallet`) + } + }) +} + +export function isSmartSessionSupported(): boolean { + const provider = ProviderUtil.getProvider(CommonConstantsUtil.CHAIN.EVM) + + if (!provider) { + return false + } + + // If it's not a WalletConnect provider, assume smart session is supported + if (!provider.isWalletConnect) { + return true + } + + // Check if the ERC7715 method is supported in the WalletConnect session + const evmNamespace = provider.session?.namespaces?.[CommonConstantsUtil.CHAIN.EVM] + const supportedMethods = evmNamespace?.methods || [] + + return supportedMethods.includes(ERC7715_METHOD) +} diff --git a/packages/experimental/src/smart-session/helper/index.ts b/packages/experimental/src/smart-session/helper/index.ts new file mode 100644 index 0000000000..3f3f4326d7 --- /dev/null +++ b/packages/experimental/src/smart-session/helper/index.ts @@ -0,0 +1,148 @@ +import { ZodError } from 'zod' +import { ERROR_MESSAGES, SmartSessionGrantPermissionsRequestSchema } from '../schema/index.js' +import { + type AddPermissionResponse, + type KeyType, + type SmartSessionGrantPermissionsRequest, + type WalletGrantPermissionsResponse +} from '../utils/TypeUtils.js' + +export function validateRequest(request: SmartSessionGrantPermissionsRequest) { + try { + return SmartSessionGrantPermissionsRequestSchema.parse(request) + } catch (e) { + if (e instanceof ZodError) { + const formattedErrors = e.errors + .map(err => `Invalid ${err.path.join('.') || 'Unknown field'}: ${err.message}`) + .join('; ') + throw new Error(formattedErrors) + } + // Re-throw the error if it's not a ZodError + throw e + } +} + +/** + * Type guard to ensure that the value is a supported CAIP address. + * @param value + * @returns + */ +export function isValidSupportedCaipAddress( + value: unknown +): value is `eip155:${string}:${string}` | `eip155:${number}:${string}` { + return ( + Boolean(value) && + typeof value === 'string' && + value.startsWith('eip155:') && + value.split(':').length === 3 + ) +} + +/** + * Extracts the chain and address component from a supported CAIP address. + * Returns `null` if the address is invalid or unsupported. + * @param caipAddress The CAIP-10 address (e.g., "eip155:1:0x...") + * @returns The extracted address or `null` + */ +export function extractChainAndAddress(caipAddress: unknown) { + if (!isValidSupportedCaipAddress(caipAddress)) { + throw new Error(ERROR_MESSAGES.UNSUPPORTED_NAMESPACE) + } + const [, chain, address] = caipAddress.split(':') + + return chain && address?.startsWith('0x') + ? { chain, address: address as `0x${string}` } + : undefined +} + +/** + * Type guard to ensure that the response is of type WalletGrantPermissionsResponse. + * Throws an error if the response does not conform to the expected structure. + */ +export function assertWalletGrantPermissionsResponse( + response: unknown +): WalletGrantPermissionsResponse { + if (!response) { + throw new Error(ERROR_MESSAGES.NO_RESPONSE_RECEIVED) + } + if ( + typeof response === 'object' && + response !== null && + 'permissions' in response && + 'context' in response && + 'expiry' in response && + 'signer' in response && + 'chainId' in response + ) { + return response as WalletGrantPermissionsResponse + } + throw new Error(ERROR_MESSAGES.INVALID_GRANT_PERMISSIONS_RESPONSE) +} + +/** + * Updates the request signer with the new cosigner key. + * If the current signer is of type 'keys', the new cosigner key is prepended to the existing keys. + * @param request SmartSessionGrantPermissionsRequest + * @param cosignerKey The new cosigner key to be added to the request signer + */ +export function updateRequestSigner( + request: SmartSessionGrantPermissionsRequest, + cosignerKey: { type: KeyType; publicKey: `0x${string}` } +) { + switch (request.signer.type) { + case 'keys': { + // If the existing signer is of type 'keys', prepend the new cosigner key + if (Array.isArray(request.signer.data?.keys)) { + request.signer.data.keys.unshift(cosignerKey) + } + break + } + + default: + throw new Error(ERROR_MESSAGES.UNSUPPORTED_SIGNER_TYPE) + } +} + +/** + * Asserts that the given response is of type AddPermissionResponse. + * + * This function performs runtime checks to ensure that the response + * matches the expected structure. It verifies that: + * - The response is an object and not null. + * - The `pci` property is a string. + * - The `key` property is an object containing: + * - `type`: A valid KeyType ('secp256k1' or 'secp256r1'). + * - `publicKey`: A string that starts with '0x'. + * + * If any of these checks fail, an error is thrown with a descriptive message. + * + * @param response - The response object to validate. + * @throws {Error} If the response does not match the expected structure. + */ +export function assertAddPermissionResponse( + response: unknown +): asserts response is AddPermissionResponse { + if (typeof response !== 'object' || response === null) { + throw new Error('Response is not an object') + } + + const { pci, key } = response as Record + + if (typeof pci !== 'string') { + throw new Error('pci is not a string') + } + + if (typeof key !== 'object' || key === null) { + throw new Error('key is not an object') + } + + const { type, publicKey } = key as Record + + if ((type as string).toLowerCase() !== 'secp256k1' && type !== 'secp256r1') { + throw new Error('Invalid key type') + } + + if (typeof publicKey !== 'string' || !publicKey.startsWith('0x')) { + throw new Error('Invalid public key format') + } +} diff --git a/packages/experimental/src/smart-session/schema/index.ts b/packages/experimental/src/smart-session/schema/index.ts new file mode 100644 index 0000000000..59c30ffd3e --- /dev/null +++ b/packages/experimental/src/smart-session/schema/index.ts @@ -0,0 +1,122 @@ +import { z } from 'zod' +import { Operation, ParamOperator } from '../utils/TypeUtils.js' + +// Define custom error messages +export const ERROR_MESSAGES = { + UNSUPPORTED_NAMESPACE: 'Unsupported namespace', + NO_RESPONSE_RECEIVED: 'No response received from grantPermissions', + INVALID_REQUEST: 'Invalid request structure', + // GrantPermissionsRequest.chainId field + INVALID_CHAIN_ID_FORMAT: 'Invalid chainId: must start with "0x"', + INVALID_CHAIN_ID_DATA: 'Invalid chainId format: Must be a hexadecimal string starting with "0x"', + // GrantPermissionsRequest.address field + INVALID_ADDRESS: 'Invalid address: must be a string starting with "0x"', + // GrantPermissionsRequest.expiry field + INVALID_EXPIRY: 'Invalid expiry: Expiry must be a future timestamp', + + // GrantPermissionsRequest.signer field - SignerSchema + INVALID_KEYS_SIGNER: 'A set of public keys is required for multiKey signers', + UNSUPPORTED_SIGNER_TYPE: 'Unsupported signer type', + UNSUPPORTED_KEY_TYPE: 'Unsupported key type: must be secp256r1 or secp256k1', + INVALID_PUBLIC_KEY_FORMAT: 'Invalid public key: must start with "0x"', + //PermissionSchema + INVALID_PERMISSIONS: 'Invalid permissions: must be a non-empty array', + INVALID_PERMISSIONS_TYPE: 'Invalid permissions: Expected array, received object', + INVALID_PERMISSIONS_TYPE_LITERALS: 'Invalid literal value, expected "contract-call"', + //PolicySchema + INVALID_POLICIES: 'Invalid policies: must be a non-empty array', + INVALID_POLICIES_TYPE: 'Invalid policies: Expected array, received object', + + INVALID_GRANT_PERMISSIONS_RESPONSE: 'Invalid grantPermissions response' +} + +// ChainId Schema +const ChainIdSchema = z + .string() + .refine(val => val.startsWith('0x'), { + message: ERROR_MESSAGES.INVALID_CHAIN_ID_FORMAT + }) + .refine(val => /^0x[0-9a-fA-F]+$/u.test(val), { + message: ERROR_MESSAGES.INVALID_CHAIN_ID_DATA + }) + +// Address Schema +const AddressSchema = z + .string({ + invalid_type_error: ERROR_MESSAGES.INVALID_ADDRESS + }) + .startsWith('0x', { message: ERROR_MESSAGES.INVALID_ADDRESS }) + .optional() + +// Expiry Schema +const ExpirySchema = z + .number() + .positive({ message: ERROR_MESSAGES.INVALID_EXPIRY }) + .refine(value => value > Math.floor(Date.now() / 1000), { + message: ERROR_MESSAGES.INVALID_EXPIRY + }) + +// Key Schema +const KeySchema = z.object({ + type: z.enum(['secp256r1', 'secp256k1'], { + errorMap: () => ({ message: ERROR_MESSAGES.UNSUPPORTED_KEY_TYPE }) + }), + publicKey: z.string().refine(val => val.startsWith('0x'), { + message: ERROR_MESSAGES.INVALID_PUBLIC_KEY_FORMAT + }) +}) + +// Signer Schema +const SignerSchema = z.object({ + type: z.literal('keys'), + data: z.object({ + keys: z.array(KeySchema).min(1, { message: ERROR_MESSAGES.INVALID_KEYS_SIGNER }) + }) +}) + +// Argument Condition Schema +const ArgumentConditionSchema = z.object({ + operator: z.nativeEnum(ParamOperator, { errorMap: () => ({ message: 'Invalid operator type' }) }), + value: z.string().startsWith('0x', { message: ERROR_MESSAGES.INVALID_ADDRESS }) +}) + +// Function Permission Schema +const FunctionPermissionSchema = z.object({ + functionName: z.string(), + args: z.array(ArgumentConditionSchema).optional(), + valueLimit: z.string().startsWith('0x', { message: ERROR_MESSAGES.INVALID_ADDRESS }).optional(), + operation: z.nativeEnum(Operation).optional() +}) + +// Contract Call Permission Schema +const ContractCallPermissionSchema = z.object({ + type: z.literal('contract-call').refine(val => val === 'contract-call', { + message: ERROR_MESSAGES.INVALID_PERMISSIONS_TYPE_LITERALS + }), + data: z.object({ + address: z.string().startsWith('0x', { message: ERROR_MESSAGES.INVALID_ADDRESS }), + abi: z.array(z.record(z.unknown())), + functions: z.array(FunctionPermissionSchema) + }) +}) + +// Policies Schema +const PolicySchema = z.object({ + type: z.string(), + data: z.record(z.unknown()) +}) + +// Smart Session Grant Permissions Request Schema +export const SmartSessionGrantPermissionsRequestSchema = z + .object({ + chainId: ChainIdSchema, + address: AddressSchema, + expiry: ExpirySchema, + signer: SignerSchema, + permissions: z + .array(ContractCallPermissionSchema) + .nonempty({ message: ERROR_MESSAGES.INVALID_PERMISSIONS }), + policies: z.array(PolicySchema) + }) + // This ensures no extra properties are allowed + .strict() diff --git a/packages/experimental/src/smart-session/utils/ConstantUtils.ts b/packages/experimental/src/smart-session/utils/ConstantUtils.ts new file mode 100644 index 0000000000..782dffe7be --- /dev/null +++ b/packages/experimental/src/smart-session/utils/ConstantUtils.ts @@ -0,0 +1,5 @@ +export const ConstantsUtil = { + COSIGNER_BASE_URL: 'https://rpc.walletconnect.org/v1/sessions' +} + +export const ERC7715_METHOD = 'wallet_grantPermissions' diff --git a/packages/experimental/src/smart-session/utils/CosignerService.ts b/packages/experimental/src/smart-session/utils/CosignerService.ts new file mode 100644 index 0000000000..677b48ba33 --- /dev/null +++ b/packages/experimental/src/smart-session/utils/CosignerService.ts @@ -0,0 +1,107 @@ +/* eslint-disable max-classes-per-file */ +import axios, { AxiosError } from 'axios' +import { ConstantsUtil } from './ConstantUtils.js' +import type { + ActivatePermissionsRequest, + AddPermissionRequest, + AddPermissionResponse +} from './TypeUtils.js' +import { assertAddPermissionResponse } from '../helper/index.js' + +// -- Custom Error Class --------------------------------------------------- // +export class CoSignerApiError extends Error { + constructor( + public status: number, + message: string + ) { + super(message) + this.name = 'CoSignerApiError' + } +} + +// -- Helper Function for API Requests ------------------------------------- // +export async function sendCoSignerRequest< + TRequest, + TResponse, + TQueryParams extends Record +>({ + url, + request, + queryParams = {} as TQueryParams, + headers, + transformRequest +}: { + url: string + request: TRequest + queryParams?: TQueryParams + headers: Record + transformRequest?: (data: TRequest) => unknown +}): Promise { + try { + const transformedData = transformRequest ? transformRequest(request) : request + const response = await axios.post(url, transformedData, { + params: queryParams, + headers + }) + + return response.data + } catch (error) { + if (axios.isAxiosError(error)) { + const axiosError = error as AxiosError + if (axiosError.response) { + throw new CoSignerApiError( + axiosError.response.status, + JSON.stringify(axiosError.response.data) + ) + } else { + throw new CoSignerApiError(500, 'Network error') + } + } + throw error + } +} + +// -- CosignerService Class ------------------------------------------ // +export class CosignerService { + private baseUrl: string + private projectId: string + + constructor(projectId: string) { + this.baseUrl = ConstantsUtil.COSIGNER_BASE_URL + if (!projectId) { + throw new Error('Project ID must be provided') + } + this.projectId = projectId + } + + async addPermission(address: string, data: AddPermissionRequest): Promise { + const url = `${this.baseUrl}/${encodeURIComponent(address)}` + + const response = await sendCoSignerRequest< + AddPermissionRequest, + AddPermissionResponse, + { projectId: string } + >({ + url, + request: data, + queryParams: { projectId: this.projectId }, + headers: { 'Content-Type': 'application/json' } + }) + assertAddPermissionResponse(response) + + return response + } + + async activatePermissions( + address: string, + updateData: ActivatePermissionsRequest + ): Promise { + const url = `${this.baseUrl}/${encodeURIComponent(address)}/activate` + await sendCoSignerRequest({ + url, + request: updateData, + queryParams: { projectId: this.projectId }, + headers: { 'Content-Type': 'application/json' } + }) + } +} diff --git a/packages/experimental/src/smart-session/utils/ERC5792Types.ts b/packages/experimental/src/smart-session/utils/ERC5792Types.ts new file mode 100644 index 0000000000..3100a9b407 --- /dev/null +++ b/packages/experimental/src/smart-session/utils/ERC5792Types.ts @@ -0,0 +1,24 @@ +type BaseCapability = { + supported: boolean +} + +export type AtomicBatchCapability = BaseCapability + +export type PaymasterServiceCapability = BaseCapability & { + url: string + context: Record | undefined +} + +export type PermissionsCapability = BaseCapability & { + signerTypes: string[] + permissionTypes: string[] + policyTypes: string[] +} + +export type ChainCapabilities = { + atomicBatch?: AtomicBatchCapability + paymasterService?: PaymasterServiceCapability + permissions?: PermissionsCapability +} + +export type WalletCapabilities = Record<`0x${string}`, ChainCapabilities> diff --git a/packages/experimental/src/smart-session/utils/TypeUtils.ts b/packages/experimental/src/smart-session/utils/TypeUtils.ts new file mode 100644 index 0000000000..ea8f39fbc6 --- /dev/null +++ b/packages/experimental/src/smart-session/utils/TypeUtils.ts @@ -0,0 +1,113 @@ +// The types of keys that are supported for the following `key` and `keys` signer types. +export type KeyType = 'secp256r1' | 'secp256k1' + +/* + * A signer representing a multisig signer. + * Each element of `publicKeys` are all explicitly the same `KeyType`, and the public keys are hex-encoded. + */ +export type MultiKeySigner = { + type: 'keys' + data: { + keys: { + type: KeyType + publicKey: `0x${string}` + }[] + } +} + +export type Signer = MultiKeySigner + +export type SmartSessionGrantPermissionsRequest = { + chainId: `0x${string}` + address?: `0x${string}` + expiry: number + signer: Signer + permissions: Permission[] + policies: { + type: string + data: Record + }[] +} + +export type WalletGrantPermissionsResponse = SmartSessionGrantPermissionsRequest & { + context: `0x${string}` + accountMeta?: { + factory: `0x${string}` + factoryData: `0x${string}` + } + signerMeta?: { + // 7679 userOp building + userOpBuilder?: `0x${string}` + // 7710 delegation + delegationManager?: `0x${string}` + } +} + +export type SmartSessionGrantPermissionsResponse = { + chainId: `0x${string}` + address: `0x${string}` + expiry: number + permissions: Permission[] + // Context is set to `pci` + context: string +} + +// Enum for parameter operators +// eslint-disable-next-line no-shadow +export enum ParamOperator { + EQUAL = 'EQUAL', + GREATER_THAN = 'GREATER_THAN', + LESS_THAN = 'LESS_THAN' +} + +// Enum for operation types +// eslint-disable-next-line no-shadow +export enum Operation { + Call = 'Call', + DelegateCall = 'DelegateCall' +} + +// Type for a single argument condition +export type ArgumentCondition = { + operator: ParamOperator + value: `0x${string}` +} + +// Type for a single function permission +export type FunctionPermission = { + // Function name + functionName: string + // An array of conditions, each corresponding to an argument for the function + args?: ArgumentCondition[] + // Maximum value that can be transferred for this specific function call + valueLimit?: `0x${string}` + // (optional) whether this is a call or a delegatecall. Defaults to call + operation?: Operation +} +export type ContractCallPermission = { + type: 'contract-call' + data: { + address: `0x${string}` + abi: Record[] + functions: FunctionPermission[] + } +} + +// Union type for all possible permissions +export type Permission = ContractCallPermission + +//--Cosigner Types----------------------------------------------------------------------- // +export type AddPermissionRequest = SmartSessionGrantPermissionsRequest + +export type AddPermissionResponse = { + pci: string + key: { + type: KeyType + publicKey: `0x${string}` + } +} + +export type ActivatePermissionsRequest = { + pci: string + context: `0x${string}` +} & AddPermissionRequest diff --git a/packages/experimental/tests/data/abi.ts b/packages/experimental/tests/data/abi.ts new file mode 100644 index 0000000000..32215f4130 --- /dev/null +++ b/packages/experimental/tests/data/abi.ts @@ -0,0 +1,15 @@ +export const donutContractAbi = [ + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256' + } + ], + name: 'purchase', + outputs: [], + stateMutability: 'payable', + type: 'function' + } +] diff --git a/packages/experimental/tests/smart-session/CosignerService.test.ts b/packages/experimental/tests/smart-session/CosignerService.test.ts new file mode 100644 index 0000000000..53f3658b84 --- /dev/null +++ b/packages/experimental/tests/smart-session/CosignerService.test.ts @@ -0,0 +1,174 @@ +import { describe, expect, it, beforeAll, beforeEach } from 'vitest' +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { + sendCoSignerRequest, + CoSignerApiError, + CosignerService +} from '../../src/smart-session/utils/CosignerService' +import { ConstantsUtil } from '../../src/smart-session/utils/ConstantUtils' +import type { + SmartSessionGrantPermissionsRequest, + ActivatePermissionsRequest +} from '../../src/smart-session/utils/TypeUtils' + +// Setup mock adapter for axios +const mock = new MockAdapter(axios) + +describe('CoSigner API Tests', () => { + const projectId = 'test-project-id' + const mockAddress = '0x1234567890123456789012345678901234567890' + let cosigner: CosignerService + + beforeAll(() => { + cosigner = new CosignerService(projectId) + }) + + beforeEach(() => { + mock.reset() + }) + + // Mock data + const mockAddPermissionRequest: SmartSessionGrantPermissionsRequest = { + chainId: '0x1', + expiry: Math.floor(Date.now() / 1000) + 3600, + signer: { + type: 'keys', + data: { + keys: [{ type: 'secp256k1', publicKey: '0x123456' }] + } + }, + permissions: [ + { + type: 'contract-call', + data: { + address: '0x2E65BAfA07238666c3b239E94F32DaD3cDD6498D', + abi: [], + functions: [] + } + } + ], + policies: [] + } + + const mockActivatePermissionsRequest: ActivatePermissionsRequest = { + ...mockAddPermissionRequest, + pci: 'test-pci', + context: '0xtest-context' + } + + describe('sendCoSignerRequest', () => { + const mockUrl = 'https://example.com' + const mockHeaders = { 'Content-Type': 'application/json' } + + it('should successfully send a request and return the response', async () => { + const mockResponse = { + pci: 'test-pci', + key: { + type: 'secp256k1', + publicKey: '0xtest-key' + } + } + mock.onPost(mockUrl).reply(200, mockResponse) + + const response = await sendCoSignerRequest({ + url: mockUrl, + request: mockAddPermissionRequest, + headers: mockHeaders, + queryParams: { projectId } + }) + + expect(response).toEqual(mockResponse) + }) + + it('should throw CoSignerApiError with a response status and message for 4xx errors', async () => { + mock.onPost(mockUrl).reply(400, { error: 'Bad Request' }) + + await expect( + sendCoSignerRequest({ + url: mockUrl, + request: mockAddPermissionRequest, + headers: mockHeaders, + queryParams: { projectId } + }) + ).rejects.toThrow(CoSignerApiError) + }) + + it('should throw CoSignerApiError for network errors', async () => { + mock.onPost(mockUrl).networkError() + + await expect( + sendCoSignerRequest({ + url: mockUrl, + request: mockAddPermissionRequest, + headers: mockHeaders, + queryParams: { projectId } + }) + ).rejects.toThrow(CoSignerApiError) + }) + }) + + describe('CosignerService', () => { + describe('addPermission', () => { + it('should successfully add a permission', async () => { + const mockResponse = { + pci: 'test-pci', + key: { + type: 'secp256k1', + publicKey: '0xtest-key' + } + } + mock + .onPost(`${ConstantsUtil.COSIGNER_BASE_URL}/${encodeURIComponent(mockAddress)}`) + .reply(200, mockResponse) + + const result = await cosigner.addPermission(mockAddress, mockAddPermissionRequest) + + expect(result).toEqual(mockResponse) + }) + + it('should handle addPermission error and throw CoSignerApiError', async () => { + mock + .onPost(`${ConstantsUtil.COSIGNER_BASE_URL}/${encodeURIComponent(mockAddress)}`) + .reply(400, { error: 'Bad Request' }) + + await expect(cosigner.addPermission(mockAddress, mockAddPermissionRequest)).rejects.toThrow( + CoSignerApiError + ) + }) + }) + + describe('activatePermissions', () => { + it('should activate permissions successfully', async () => { + mock + .onPost(`${ConstantsUtil.COSIGNER_BASE_URL}/${encodeURIComponent(mockAddress)}/activate`) + .reply(200) + + await expect( + cosigner.activatePermissions(mockAddress, mockActivatePermissionsRequest) + ).resolves.toBeUndefined() + }) + + it('should handle activatePermissions error and throw CoSignerApiError', async () => { + mock + .onPost(`${ConstantsUtil.COSIGNER_BASE_URL}/${encodeURIComponent(mockAddress)}/activate`) + .reply(400, { error: 'Bad Request' }) + + await expect( + cosigner.activatePermissions(mockAddress, mockActivatePermissionsRequest) + ).rejects.toThrow(CoSignerApiError) + }) + }) + }) + + describe('CosignerService Constructor', () => { + it('should throw an error if projectId is not provided', () => { + expect(() => new CosignerService('')).toThrow('Project ID must be provided') + }) + + it('should create an instance with a valid projectId', () => { + const instance = new CosignerService('valid-project-id') + expect(instance).toBeInstanceOf(CosignerService) + }) + }) +}) diff --git a/packages/experimental/tests/smart-session/grantPermissions.test.ts b/packages/experimental/tests/smart-session/grantPermissions.test.ts new file mode 100644 index 0000000000..5573d51261 --- /dev/null +++ b/packages/experimental/tests/smart-session/grantPermissions.test.ts @@ -0,0 +1,301 @@ +import { describe, expect, it, beforeEach, vi } from 'vitest' +import { grantPermissions } from '../../src/smart-session/grantPermissions' +import { + ChainController, + ConnectionController, + OptionsController, + type ConnectionControllerClient +} from '@reown/appkit-core' +import { ProviderUtil } from '@reown/appkit/store' +import { CosignerService } from '../../src/smart-session/utils/CosignerService' +import type { SmartSessionGrantPermissionsRequest } from '../../src/smart-session/utils/TypeUtils.js' +import { + extractChainAndAddress, + isValidSupportedCaipAddress, + assertWalletGrantPermissionsResponse +} from '../../src/smart-session/helper/index.js' +import { donutContractAbi } from '../data/abi' +import { ERROR_MESSAGES } from '../../src/smart-session/schema' + +vi.mock('@reown/appkit-core') +vi.mock('@reown/appkit/store') +vi.mock('../../src/smart-session/utils/CosignerService') + +describe('grantPermissions', () => { + let mockRequest: SmartSessionGrantPermissionsRequest + const expiry = Date.now() + 1000 + beforeEach(() => { + ;(mockRequest = { + address: '0x1234567890123456789012345678901234567890', + chainId: '0x1', + expiry: expiry, + signer: { + type: 'keys', + data: { + keys: [{ type: 'secp256k1', publicKey: '0x123456' }] + } + }, + permissions: [ + { + type: 'contract-call', + data: { + address: '0x2E65BAfA07238666c3b239E94F32DaD3cDD6498D', + abi: donutContractAbi, + functions: [ + { + functionName: 'purchase' + } + ] + } + } + ], + policies: [] + }), + vi.resetAllMocks() + vi.mocked(OptionsController.state).projectId = 'test-project-id' + vi.mocked(ChainController.state).activeCaipAddress = + 'eip155:1:0x1234567890123456789012345678901234567890' + vi.mocked(ProviderUtil).getProvider = vi.fn().mockReturnValue({ + session: { + namespaces: { + eip155: { + methods: ['wallet_grantPermissions'] + } + } + } + }) + vi.mocked(CosignerService.prototype.addPermission).mockResolvedValue({ + pci: 'test-pci', + key: { + type: 'secp256k1', + publicKey: '0xtest-key' + } + }) + + vi.mocked(ConnectionController._getClient).mockReturnValue({ + getCapabilities: vi.fn().mockResolvedValue({ + '0x1': { + permissions: { + supported: true, + permissionTypes: ['contract-call'], + signerTypes: ['keys'], + policyTypes: [] + } + } + }), + grantPermissions: vi.fn().mockResolvedValue({ + permissions: [ + { + type: 'contract-call', + data: { + address: '0x2E65BAfA07238666c3b239E94F32DaD3cDD6498D', + abi: donutContractAbi, + functions: [ + { + functionName: 'purchase' + } + ] + } + } + ], + signer: { + type: 'keys', + data: { + keys: [ + { type: 'secp256k1', publicKey: '0xtest-key' }, + { type: 'secp256k1', publicKey: '0x123456' } + ] + } + }, + context: '0xcontext', + expiry: expiry, + address: '0x1234567890123456789012345678901234567890', + chainId: '0x1' + }) + } as unknown as ConnectionControllerClient) + + vi.mocked(CosignerService.prototype.activatePermissions).mockResolvedValue(undefined) + }) + + it('should successfully grant permissions and invoke required methods', async () => { + const result = await grantPermissions(mockRequest) + + expect(result).toEqual({ + permissions: [ + { + type: 'contract-call', + data: { + address: '0x2E65BAfA07238666c3b239E94F32DaD3cDD6498D', + abi: donutContractAbi, + functions: [ + { + functionName: 'purchase' + } + ] + } + } + ], + context: 'test-pci', + expiry: expiry, + address: '0x1234567890123456789012345678901234567890', + chainId: '0x1' + }) + expect(CosignerService.prototype.addPermission).toHaveBeenCalledWith( + 'eip155:1:0x1234567890123456789012345678901234567890', + mockRequest + ) + expect(CosignerService.prototype.activatePermissions).toHaveBeenCalled() + }) + + it('should throw an error for unsupported namespace', async () => { + vi.mocked(ChainController.state).activeCaipAddress = undefined + await expect(grantPermissions(mockRequest)).rejects.toThrow( + ERROR_MESSAGES.UNSUPPORTED_NAMESPACE + ) + }) + + it('should throw an error when grantPermissions returns null', async () => { + vi.mocked(ConnectionController._getClient).mockReturnValueOnce({ + getCapabilities: vi.fn().mockResolvedValue({ + '0x1': { + permissions: { + supported: true, + permissionTypes: ['contract-call'], + signerTypes: ['keys'], + policyTypes: [] + } + } + }), + grantPermissions: vi.fn().mockResolvedValue(null) + } as unknown as ConnectionControllerClient) + + await expect(grantPermissions(mockRequest)).rejects.toThrow(ERROR_MESSAGES.NO_RESPONSE_RECEIVED) + }) + + it('should handle network errors', async () => { + vi.mocked(CosignerService.prototype.addPermission).mockRejectedValue(new Error('Network error')) + await expect(grantPermissions(mockRequest)).rejects.toThrow('Network error') + }) + + it('should throw an error for invalid chainId format', async () => { + const invalidRequest = { ...mockRequest, chainId: '1' } // chainId should start with '0x' + await expect(grantPermissions(invalidRequest as any)).rejects.toThrow( + ERROR_MESSAGES.INVALID_CHAIN_ID_FORMAT + ) + }) + + it('should throw an error for invalid expiry', async () => { + const invalidRequest = { ...mockRequest, expiry: -1 } + await expect(grantPermissions(invalidRequest)).rejects.toThrow(ERROR_MESSAGES.INVALID_EXPIRY) + }) + + it('should throw an error for empty permissions', async () => { + const invalidRequest = { ...mockRequest, permissions: [] } + await expect(grantPermissions(invalidRequest)).rejects.toThrow( + ERROR_MESSAGES.INVALID_PERMISSIONS + ) + }) + + it('should throw an error for invalid policies', async () => { + const invalidRequest = { ...mockRequest, policies: {} as any } + await expect(grantPermissions(invalidRequest)).rejects.toThrow( + ERROR_MESSAGES.INVALID_POLICIES_TYPE + ) + }) + + it('should throw an error for invalid signer', async () => { + const invalidRequest = { ...mockRequest, signer: null as any } + await expect(grantPermissions(invalidRequest)).rejects.toThrow() + }) + + it('should throw an error for invalid signer type', async () => { + const invalidRequest = { ...mockRequest, signer: { type: {}, data: {} } as any } + await expect(grantPermissions(invalidRequest)).rejects.toThrow() + }) + + it('should successfully update the signer in request for valid key signer', async () => { + await grantPermissions(mockRequest) + expect(mockRequest.signer).toEqual({ + type: 'keys', + data: { + keys: [ + { type: 'secp256k1', publicKey: '0xtest-key' }, + { type: 'secp256k1', publicKey: '0x123456' } + ] + } + }) + }) + + it('should not modify the signer in request if signer type is already "keys"', async () => { + mockRequest.signer = { + type: 'keys', + data: { keys: [{ type: 'secp256k1', publicKey: '0xexisting-key' }] } + } + await grantPermissions(mockRequest) + expect(mockRequest.signer).toEqual({ + type: 'keys', + data: { + keys: [ + { type: 'secp256k1', publicKey: '0xtest-key' }, + { type: 'secp256k1', publicKey: '0xexisting-key' } + ] + } + }) + }) + + it('should throw an error when CosignerService addPermission fails', async () => { + vi.mocked(CosignerService.prototype.addPermission).mockRejectedValue( + new Error('Cosigner error') + ) + await expect(grantPermissions(mockRequest)).rejects.toThrow('Cosigner error') + }) + + it('should throw an error when ConnectionController grantPermissions fails', async () => { + vi.mocked(ConnectionController._getClient).mockReturnValueOnce({ + getCapabilities: vi.fn().mockResolvedValue({ + '0x1': { + permissions: { + supported: true, + permissionTypes: ['contract-call'], + signerTypes: ['keys'], + policyTypes: [] + } + } + }), + grantPermissions: vi.fn().mockRejectedValue(new Error('Connection error')) + } as unknown as ConnectionControllerClient) + + await expect(grantPermissions(mockRequest)).rejects.toThrow('Connection error') + }) + + it('should return undefined for an invalid address in extractAddress', () => { + const invalidCaipAddress = 'eip155:1:invalid-address' + const result = extractChainAndAddress(invalidCaipAddress) + expect(result).toBeUndefined() + }) + + it('should return a valid 0x-prefixed address from extractAddress', () => { + const validCaipAddress = 'eip155:1:0x1234567890123456789012345678901234567890' + const result = extractChainAndAddress(validCaipAddress) + expect(result?.address).toEqual('0x1234567890123456789012345678901234567890') + }) + + it('should return true for a valid CAIP address in isValidSupportedCaipAddress', () => { + const validCaipAddress = 'eip155:1:0x1234567890123456789012345678901234567890' + const isValid = isValidSupportedCaipAddress(validCaipAddress) + expect(isValid).toBe(true) + }) + + it('should return false for an invalid CAIP address in isValidSupportedCaipAddress', () => { + const invalidCaipAddress = 'invalid:namespace:0x1234567890123456789012345678901234567890' + const isValid = isValidSupportedCaipAddress(invalidCaipAddress) + expect(isValid).toBe(false) + }) + + it('should throw an error for an invalid response in assertWalletGrantPermissionsResponse', () => { + const invalidResponse = { invalid: 'data' } + expect(() => assertWalletGrantPermissionsResponse(invalidResponse)).toThrow( + ERROR_MESSAGES.INVALID_GRANT_PERMISSIONS_RESPONSE + ) + }) +}) diff --git a/packages/experimental/tests/smart-session/requestValidation.test.ts b/packages/experimental/tests/smart-session/requestValidation.test.ts new file mode 100644 index 0000000000..e56a9e362b --- /dev/null +++ b/packages/experimental/tests/smart-session/requestValidation.test.ts @@ -0,0 +1,511 @@ +import { describe, expect, it, beforeEach } from 'vitest' +import type { + ContractCallPermission, + MultiKeySigner, + SmartSessionGrantPermissionsRequest +} from '../../src/smart-session/utils/TypeUtils.js' +import { donutContractAbi } from '../data/abi.js' +import { validateRequest } from '../../src/smart-session/helper/index.js' +import { ERROR_MESSAGES } from '../../src/smart-session/schema/index.js' + +describe('smart-session/schema', () => { + let mockRequest: SmartSessionGrantPermissionsRequest + + beforeEach(() => { + mockRequest = { + chainId: '0x1', + expiry: Date.now() + 10000, + signer: { + type: 'keys', + data: { + keys: [{ type: 'secp256k1', publicKey: '0x123456' }] + } + }, + permissions: [ + { + type: 'contract-call', + data: { + address: '0x2E65BAfA07238666c3b239E94F32DaD3cDD6498D', + abi: donutContractAbi, + functions: [ + { + functionName: 'purchase' + } + ] + } + } + ], + policies: [] + } + }) + + describe('validateRequest', () => { + describe('request object validation', () => { + it('should pass for valid request', () => { + expect(() => validateRequest(mockRequest)).not.toThrow() + }) + it('should fail for missing chainId', () => { + const { chainId, ...requestWithoutChainId } = mockRequest + expect(() => validateRequest(requestWithoutChainId as any)).toThrow( + 'Invalid chainId: Required' + ) + }) + }) + describe('ChainIdSchema Validation', () => { + it('should pass for valid chainId', () => { + const request = { ...mockRequest, chainId: '0x1' as `0x${string}` } + expect(() => validateRequest(request)).not.toThrow() + }) + + it.each([ + [null, 'null'], + [undefined, 'undefined'], + [123, 'number'], + [{}, 'object'], + [[], 'array'], + ['', 'empty string'], + ['123456', 'string without 0x prefix'], + ['0x', 'only 0x'], + ['0xZZZZZZ', 'invalid hexadecimal'] + ])('should fail for %s chainId', (chainId, _description) => { + const request = { ...mockRequest, chainId } + if (chainId === undefined) { + const { chainId, ...requestWithoutChainId } = request + expect(() => validateRequest(requestWithoutChainId as any)).toThrow(/Invalid chainId/) + } else { + expect(() => validateRequest(request as any)).toThrow(/Invalid chainId/) + } + }) + + it('should handle chainIds with leading zeros', () => { + const request = { ...mockRequest, chainId: '0x01' as `0x${string}` } + expect(() => validateRequest(request)).not.toThrow() + }) + + it('should be case insensitive for hex characters', () => { + const request1 = { ...mockRequest, chainId: '0xaB1' as `0x${string}` } + const request2 = { ...mockRequest, chainId: '0xAb1' as `0x${string}` } + expect(() => validateRequest(request1)).not.toThrow() + expect(() => validateRequest(request2)).not.toThrow() + }) + }) + describe('Address field validation', () => { + it('should pass for valid Ethereum address', () => { + const request = { + ...mockRequest, + address: '0x1234567890123456789012345678901234567890' as `0x${string}` + } + expect(() => validateRequest(request)).not.toThrow() + }) + + it('should pass when address is omitted', () => { + const { address, ...requestWithoutAddress } = mockRequest + expect(() => validateRequest(requestWithoutAddress)).not.toThrow() + }) + + it('should fail for address not starting with 0x', () => { + const request = { + ...mockRequest, + address: '1234567890123456789012345678901234567890' as `0x${string}` + } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_ADDRESS) + }) + + it('should fail for empty string address', () => { + const request = { ...mockRequest, address: '' as any } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_ADDRESS) + }) + + it('should fail for non-string address', () => { + const request = { ...mockRequest, address: 123 as any } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_ADDRESS) + }) + + it('should fail for null address', () => { + const request = { ...mockRequest, address: null as any } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_ADDRESS) + }) + + it('should fail for undefined address', () => { + const request = { ...mockRequest, address: undefined } + expect(() => validateRequest(request)).not.toThrow() // Because it's optional + }) + + it('should be case insensitive for hex characters', () => { + const request = { ...mockRequest, address: ('0xAbCd' + '0'.repeat(36)) as `0x${string}` } + expect(() => validateRequest(request)).not.toThrow() + }) + + // TODO: + // Should fail for address shorter than 42 characters + // Should fail for address longer than 42 characters + // Should fail for address with spaces' + }) + + describe('Expiry field validation', () => { + const currentTimestamp = Math.floor(Date.now() / 1000) + + it('should pass for a valid future expiry', () => { + const request = { ...mockRequest, expiry: currentTimestamp + 3600 } // 1 hour in the future + expect(() => validateRequest(request)).not.toThrow() + }) + + it('should fail for a negative expiry', () => { + const request = { ...mockRequest, expiry: -1 } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_EXPIRY) + }) + + it('should fail for a zero expiry', () => { + const request = { ...mockRequest, expiry: 0 } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_EXPIRY) + }) + + it('should fail for a past expiry', () => { + const request = { ...mockRequest, expiry: currentTimestamp - 3600 } // 1 hour in the past + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_EXPIRY) + }) + + it('should fail for a current timestamp expiry', () => { + const request = { ...mockRequest, expiry: currentTimestamp } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_EXPIRY) + }) + + it('should fail for a non-number expiry', () => { + const request = { ...mockRequest, expiry: '1234567890' as any } + expect(() => validateRequest(request)).toThrow( + 'Invalid expiry: Expected number, received string' + ) + }) + + it('should fail for an undefined expiry', () => { + const { expiry, ...requestWithoutExpiry } = mockRequest + expect(() => validateRequest(requestWithoutExpiry as any)).toThrow( + 'Invalid expiry: Required' + ) + }) + + it('should fail for a null expiry', () => { + const request = { ...mockRequest, expiry: null as any } + expect(() => validateRequest(request)).toThrow( + 'Invalid expiry: Expected number, received null' + ) + }) + + it('should pass for an expiry 1 hour in the future', () => { + const request = { ...mockRequest, expiry: currentTimestamp + 3600 } + expect(() => validateRequest(request)).not.toThrow() + }) + }) + + describe('Signer field validation', () => { + it('should fail for unsupported signer', () => { + const request = { + ...mockRequest, + signer: { + type: 'key', + data: { type: 'secp256k1', publicKey: '0x1234567890abcdef' as `0x${string}` } + } as any + } + expect(() => validateRequest(request)).toThrow() + }) + + it('should pass for valid multi-key signer', () => { + const request = { + ...mockRequest, + signer: { + type: 'keys', + data: { + keys: [ + { type: 'secp256k1', publicKey: '0x1234567890abcdef' }, + { type: 'secp256r1', publicKey: '0xabcdef1234567890' } + ] + } + } as MultiKeySigner + } + expect(() => validateRequest(request)).not.toThrow() + }) + + it('should fail for empty keys array in multi-key signer', () => { + const request = { + ...mockRequest, + signer: { type: 'keys', data: { keys: [] } } as any + } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_KEYS_SIGNER) + }) + + it('should fail for missing data in multi-key signer', () => { + const request = { + ...mockRequest, + signer: { type: 'keys' } as any + } + expect(() => validateRequest(request)).toThrow('Invalid signer.data: Required') + }) + + it('should fail for missing keys in multi-key signer', () => { + const request = { + ...mockRequest, + signer: { type: 'keys', data: {} } as any + } + expect(() => validateRequest(request)).toThrow('Invalid signer.data.keys: Required') + }) + + it('should fail for non-object signer', () => { + const request = { + ...mockRequest, + signer: 'invalid' as any + } + expect(() => validateRequest(request)).toThrow( + 'Invalid signer: Expected object, received string' + ) + }) + + it('should fail for null signer', () => { + const request = { + ...mockRequest, + signer: null as any + } + expect(() => validateRequest(request)).toThrow( + 'Invalid signer: Expected object, received null' + ) + }) + + it('should fail for signer with invalid key type', () => { + const request = { + ...mockRequest, + signer: { + type: 'keys', + data: { + keys: [{ type: 'invalid', publicKey: '0x1234567890abcdef' as `0x${string}` }] + } + } as any + } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.UNSUPPORTED_KEY_TYPE) + }) + + it('should fail for signer with invalid public key format', () => { + const request = { + ...mockRequest, + signer: { + type: 'keys', + data: { + keys: [{ type: 'secp256k1', publicKey: 'invalid' as `0x${string}` }] + } + } as MultiKeySigner + } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_PUBLIC_KEY_FORMAT) + }) + }) + + describe('Permissions field validation', () => { + const validPermission = { + type: 'contract-call', + data: { + address: '0x1234567890123456789012345678901234567890', + abi: [{}], + functions: [{ functionName: 'testFunction' }] + } + } as ContractCallPermission + + it('should pass for valid permissions array', () => { + const request = { + ...mockRequest, + permissions: [validPermission] + } + expect(() => validateRequest(request)).not.toThrow() + }) + + it('should pass for multiple valid permissions', () => { + const request = { + ...mockRequest, + permissions: [validPermission, validPermission] + } + expect(() => validateRequest(request)).not.toThrow() + }) + + it('should fail for empty permissions array', () => { + const request = { + ...mockRequest, + permissions: [] + } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_PERMISSIONS) + }) + + it('should fail for non-array permissions', () => { + const request = { + ...mockRequest, + permissions: validPermission as any + } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_PERMISSIONS_TYPE) + }) + + it('should fail for invalid permission type', () => { + const request = { + ...mockRequest, + permissions: [{ ...validPermission, type: 'invalid-type' }] as any + } + expect(() => validateRequest(request)).toThrow( + `Invalid permissions.0.type: ${ERROR_MESSAGES.INVALID_PERMISSIONS_TYPE_LITERALS}` + ) + }) + + it('should fail for missing address in permission data', () => { + const invalidPermission = { + ...validPermission, + data: { ...validPermission.data, address: undefined } + } + const request = { + ...mockRequest, + permissions: [invalidPermission] as any + } + expect(() => validateRequest(request)).toThrow( + 'Invalid permissions.0.data.address: Required' + ) + }) + + it('should fail for invalid address format in permission data', () => { + const invalidPermission = { + ...validPermission, + data: { ...validPermission.data, address: '1234' } + } + const request = { + ...mockRequest, + permissions: [invalidPermission] as any + } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_ADDRESS) + }) + + it('should fail for missing abi in permission data', () => { + const invalidPermission = { + ...validPermission, + data: { ...validPermission.data, abi: undefined } + } + const request = { + ...mockRequest, + permissions: [invalidPermission] as any + } + expect(() => validateRequest(request)).toThrow('Invalid permissions.0.data.abi: Required') + }) + + it('should fail for non-array abi in permission data', () => { + const invalidPermission = { + ...validPermission, + data: { ...validPermission.data, abi: {} as any } + } + const request = { + ...mockRequest, + permissions: [invalidPermission] + } + expect(() => validateRequest(request)).toThrow( + 'Invalid permissions.0.data.abi: Expected array, received object' + ) + }) + + it('should fail for missing functions in permission data', () => { + const invalidPermission = { + ...validPermission, + data: { ...validPermission.data, functions: undefined } + } + const request = { + ...mockRequest, + permissions: [invalidPermission] as any + } + expect(() => validateRequest(request)).toThrow( + 'Invalid permissions.0.data.functions: Required' + ) + }) + + it('should fail for non-array functions in permission data', () => { + const invalidPermission = { + ...validPermission, + data: { ...validPermission.data, functions: {} as any } + } + const request = { + ...mockRequest, + permissions: [invalidPermission] + } + expect(() => validateRequest(request)).toThrow( + 'Invalid permissions.0.data.functions: Expected array, received object' + ) + }) + + it('should fail for missing functionName in function permission', () => { + const invalidPermission = { + ...validPermission, + data: { ...validPermission.data, functions: [{}] } + } + const request = { + ...mockRequest, + permissions: [invalidPermission] as any + } + expect(() => validateRequest(request)).toThrow( + 'Invalid permissions.0.data.functions.0.functionName: Required' + ) + }) + }) + + describe('Policy field validation', () => { + it('should pass for valid policies array', () => { + const request = { + ...mockRequest, + policies: [ + { type: 'someType', data: { someKey: 'someValue' } }, + { type: 'anotherType', data: { anotherKey: 'anotherValue' } } + ] + } + expect(() => validateRequest(request)).not.toThrow() + }) + + it('should pass for empty policies array', () => { + const request = { + ...mockRequest, + policies: [] + } + expect(() => validateRequest(request)).not.toThrow() + }) + + it('should fail for non-array policies', () => { + const request = { + ...mockRequest, + policies: { type: 'someType', data: {} } as any + } + expect(() => validateRequest(request)).toThrow(ERROR_MESSAGES.INVALID_POLICIES_TYPE) + }) + + it('should fail for policies with invalid structure', () => { + const request = { + ...mockRequest, + policies: [{ invalidKey: 'value' }] as any + } + expect(() => validateRequest(request)).toThrow( + 'Invalid policies.0.type: Required; Invalid policies.0.data: Required' + ) + }) + + it('should fail for policies with missing type', () => { + const request = { + ...mockRequest, + policies: [{ data: { key: 'value' } }] as any + } + expect(() => validateRequest(request)).toThrow('Invalid policies.0.type: Required') + }) + + it('should fail for policies with missing data', () => { + const request = { + ...mockRequest, + policies: [{ type: 'someType' }] as any + } + expect(() => validateRequest(request)).toThrow('Invalid policies.0.data: Required') + }) + + it('should fail for policies with non-object data', () => { + const request = { + ...mockRequest, + policies: [{ type: 'someType', data: 'invalidData' }] as any + } + expect(() => validateRequest(request)).toThrow( + 'Invalid policies.0.data: Expected object, received string' + ) + }) + }) + }) +}) diff --git a/packages/experimental/tsconfig.build.json b/packages/experimental/tsconfig.build.json new file mode 100644 index 0000000000..d736f2cb0f --- /dev/null +++ b/packages/experimental/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["**/tests"] +} diff --git a/packages/experimental/tsconfig.json b/packages/experimental/tsconfig.json new file mode 100644 index 0000000000..d9c0658fc0 --- /dev/null +++ b/packages/experimental/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "outDir": "./dist/esm", + "declarationDir": "./dist/types" + }, + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts", "tests", "exports"] +} diff --git a/packages/experimental/vitest.config.ts b/packages/experimental/vitest.config.ts new file mode 100644 index 0000000000..25438b4718 --- /dev/null +++ b/packages/experimental/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import { configDefaults } from 'vitest/config' + +export default defineConfig({ + test: { + ...configDefaults, + globals: true, + environment: 'jsdom' + } +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 761bdc1bb2..57c3fab27e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,7 +27,7 @@ importers: version: 6.18.1(eslint@8.56.0)(typescript@5.3.3) '@vitest/coverage-v8': specifier: 1.1.2 - version: 1.1.2(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 1.1.2(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) danger: specifier: 11.3.1 version: 11.3.1 @@ -219,6 +219,9 @@ importers: '@reown/appkit-ethers5': specifier: workspace:* version: link:../../packages/ethers5 + '@reown/appkit-experimental': + specifier: workspace:* + version: link:../../packages/experimental '@reown/appkit-siwe': specifier: workspace:* version: link:../../packages/siwe @@ -264,6 +267,9 @@ importers: bs58: specifier: 6.0.0 version: 6.0.0 + date-fns: + specifier: 4.1.0 + version: 4.1.0 ethers: specifier: 6.13.2 version: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -663,7 +669,7 @@ importers: version: 0.1.14(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@solana/wallet-adapter-wallets': specifier: 0.19.32 - version: 0.19.32(@babel/core@7.25.2)(@babel/runtime@7.25.6)(@sentry/types@7.119.1)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)(tslib@2.7.0)(utf-8-validate@5.0.10) + version: 0.19.32(@babel/core@7.25.2)(@babel/runtime@7.25.6)(@sentry/types@7.119.1)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bs58@6.0.0)(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)(tslib@2.7.0)(utf-8-validate@5.0.10) '@tanstack/react-query': specifier: 5.24.8 version: 5.24.8(react@18.2.0) @@ -762,7 +768,7 @@ importers: version: 0.1.14(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@solana/wallet-adapter-wallets': specifier: 0.19.32 - version: 0.19.32(@babel/core@7.25.2)(@babel/runtime@7.25.6)(@sentry/types@7.119.1)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) + version: 0.19.32(@babel/core@7.25.2)(@babel/runtime@7.25.6)(@sentry/types@7.119.1)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)(tslib@2.7.0)(utf-8-validate@5.0.10) vue: specifier: 3.4.3 version: 3.4.3(typescript@5.3.3) @@ -849,7 +855,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) '@walletconnect/types': specifier: 2.17.0 version: 2.17.0 @@ -907,7 +913,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) '@walletconnect/types': specifier: 2.17.0 version: 2.17.0 @@ -929,7 +935,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) vitest: specifier: 2.0.5 version: 2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) @@ -1015,7 +1021,7 @@ importers: version: 18.2.0 '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) '@vue/runtime-core': specifier: 3.4.3 version: 3.4.3 @@ -1094,7 +1100,7 @@ importers: version: 18.2.0 '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) '@walletconnect/types': specifier: 2.17.0 version: 2.17.0 @@ -1152,7 +1158,7 @@ importers: version: 18.2.0 '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) '@vue/runtime-core': specifier: 3.4.3 version: 3.4.3 @@ -1204,7 +1210,7 @@ importers: version: 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) vitest: specifier: 2.0.5 version: 2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) @@ -1260,7 +1266,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) vitest: specifier: 2.0.5 version: 2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) @@ -1285,7 +1291,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) vitest: specifier: 2.0.5 version: 2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) @@ -1385,6 +1391,37 @@ importers: specifier: 3.4.3 version: 3.4.3(typescript@5.3.3) + packages/experimental: + dependencies: + '@reown/appkit': + specifier: workspace:* + version: link:../appkit + '@reown/appkit-common': + specifier: workspace:* + version: link:../common + '@reown/appkit-core': + specifier: workspace:* + version: link:../core + axios: + specifier: 1.7.2 + version: 1.7.2 + zod: + specifier: 3.22.4 + version: 3.22.4 + devDependencies: + '@types/axios-mock-adapter': + specifier: 1.10.0 + version: 1.10.0(axios@1.7.2) + '@vitest/coverage-v8': + specifier: 2.0.5 + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) + axios-mock-adapter: + specifier: 2.0.0 + version: 2.0.0(axios@1.7.2) + vitest: + specifier: 2.0.5 + version: 2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) + packages/polyfills: dependencies: buffer: @@ -1421,7 +1458,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: '2' - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) packages/siwe: dependencies: @@ -1533,7 +1570,7 @@ importers: version: 5.1.5 '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) '@walletconnect/types': specifier: 2.17.0 version: 2.17.0 @@ -1564,7 +1601,7 @@ importers: version: 1.5.5 '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) eslint-plugin-lit: specifier: 1.11.0 version: 1.11.0(eslint@8.57.0) @@ -1644,7 +1681,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0)) + version: 2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0)) jsdom: specifier: 24.1.0 version: 24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -6443,6 +6480,10 @@ packages: peerDependencies: tslib: ^2.6.2 + '@types/axios-mock-adapter@1.10.0': + resolution: {integrity: sha512-Govyvy3cr8hsR6DcCoBjVMQhYyzkHdfU/hMgBw6ONOFuTrZTDxdE/rBR7Sz3qsGtVZABSK8/2rDekp5UwZ/V8A==} + deprecated: This is a stub types definition for axios-mock-adapter (https://github.com/ctimmerm/axios-mock-adapter). axios-mock-adapter provides its own type definitions, so you don't need @types/axios-mock-adapter installed! + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -7269,6 +7310,11 @@ packages: resolution: {integrity: sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==} engines: {node: '>=4'} + axios-mock-adapter@2.0.0: + resolution: {integrity: sha512-D/K0J5Zm6KvaMTnsWrBQZWLzKN9GxUFZEa0mx2qeEHXDeTugCoplWehy8y36dj5vuSjhe1u/Dol8cZ8lzzmDew==} + peerDependencies: + axios: '>= 0.17.0' + axios@1.6.7: resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} @@ -7971,6 +8017,9 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.10: resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} @@ -9304,6 +9353,10 @@ packages: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + is-bun-module@1.2.1: resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} @@ -17666,23 +17719,6 @@ snapshots: optionalDependencies: '@types/react': 18.2.62 - '@react-native/virtualized-lists@0.75.3(@types/react@18.2.62)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)': - dependencies: - invariant: 2.2.4 - nullthrows: 1.1.1 - react: 18.2.0 - react-native: 0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10) - optionalDependencies: - '@types/react': 18.2.62 - optional: true - - '@react-native/virtualized-lists@0.75.3(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))': - dependencies: - invariant: 2.2.4 - nullthrows: 1.1.1 - react-native: 0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10) - optional: true - '@rollup/plugin-inject@5.0.5(rollup@4.22.4)': dependencies: '@rollup/pluginutils': 5.1.2(rollup@4.22.4) @@ -18407,40 +18443,6 @@ snapshots: - tslib - utf-8-validate - '@solana/wallet-adapter-trezor@0.1.2(@babel/core@7.25.2)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@solana/wallet-adapter-base': 0.9.23(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@trezor/connect-web': 9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) - buffer: 6.0.3 - transitivePeerDependencies: - - '@babel/core' - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - supports-color - - tslib - - utf-8-validate - - '@solana/wallet-adapter-trezor@0.1.2(@babel/core@7.25.2)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@solana/wallet-adapter-base': 0.9.23(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@trezor/connect-web': 9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) - buffer: 6.0.3 - transitivePeerDependencies: - - '@babel/core' - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - supports-color - - tslib - - utf-8-validate - '@solana/wallet-adapter-trust@0.1.13(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))': dependencies: '@solana/wallet-adapter-base': 0.9.23(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) @@ -18546,76 +18548,7 @@ snapshots: - uWebSockets.js - utf-8-validate - '@solana/wallet-adapter-wallets@0.19.32(@babel/core@7.25.2)(@babel/runtime@7.25.6)(@sentry/types@7.119.1)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@solana/wallet-adapter-alpha': 0.1.10(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-avana': 0.1.13(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-bitkeep': 0.3.20(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-bitpie': 0.5.18(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-clover': 0.4.19(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-coin98': 0.5.20(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-coinbase': 0.1.19(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-coinhub': 0.3.18(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-fractal': 0.1.8(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@solana/wallet-adapter-huobi': 0.1.15(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-hyperpay': 0.1.14(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-keystone': 0.1.15(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-krystal': 0.1.12(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-ledger': 0.9.25(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-mathwallet': 0.9.18(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-neko': 0.2.12(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-nightly': 0.1.16(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-nufi': 0.1.17(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-onto': 0.1.7(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-particle': 0.1.12(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bs58@6.0.0) - '@solana/wallet-adapter-phantom': 0.9.24(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-safepal': 0.5.18(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-saifu': 0.1.15(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-salmon': 0.1.14(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-sky': 0.1.15(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-solflare': 0.6.28(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-solong': 0.9.18(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-spot': 0.1.15(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-tokenary': 0.1.12(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-tokenpocket': 0.4.19(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-torus': 0.11.28(@babel/runtime@7.25.6)(@sentry/types@7.119.1)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-trezor': 0.1.2(@babel/core@7.25.2)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-trust': 0.1.13(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-unsafe-burner': 0.1.7(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-walletconnect': 0.1.16(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-xdefi': 0.1.7(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@babel/core' - - '@babel/runtime' - - '@capacitor/preferences' - - '@netlify/blobs' - - '@planetscale/database' - - '@react-native-async-storage/async-storage' - - '@sentry/types' - - '@upstash/redis' - - '@vercel/kv' - - bs58 - - bufferutil - - encoding - - expo-constants - - expo-localization - - ioredis - - react - - react-dom - - react-native - - supports-color - - tslib - - uWebSockets.js - - utf-8-validate - - '@solana/wallet-adapter-wallets@0.19.32(@babel/core@7.25.2)(@babel/runtime@7.25.6)(@sentry/types@7.119.1)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': + '@solana/wallet-adapter-wallets@0.19.32(@babel/core@7.25.2)(@babel/runtime@7.25.6)(@sentry/types@7.119.1)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)(tslib@2.7.0)(utf-8-validate@5.0.10)': dependencies: '@solana/wallet-adapter-alpha': 0.1.10(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@solana/wallet-adapter-avana': 0.1.13(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) @@ -18648,7 +18581,7 @@ snapshots: '@solana/wallet-adapter-tokenary': 0.1.12(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@solana/wallet-adapter-tokenpocket': 0.4.19(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@solana/wallet-adapter-torus': 0.11.28(@babel/runtime@7.25.6)(@sentry/types@7.119.1)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-trezor': 0.1.2(@babel/core@7.25.2)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-trezor': 0.1.2(@babel/core@7.25.2)(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) '@solana/wallet-adapter-trust': 0.1.13(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@solana/wallet-adapter-unsafe-burner': 0.1.7(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@solana/wallet-adapter-walletconnect': 0.1.16(@solana/web3.js@1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -19607,26 +19540,6 @@ snapshots: - expo-localization - react-native - '@trezor/analytics@1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': - dependencies: - '@trezor/env-utils': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - expo-constants - - expo-localization - - react-native - - '@trezor/analytics@1.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': - dependencies: - '@trezor/env-utils': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - expo-constants - - expo-localization - - react-native - '@trezor/blockchain-link-types@1.2.0(bufferutil@4.0.8)(tslib@2.7.0)(utf-8-validate@5.0.10)': dependencies: '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -19655,36 +19568,6 @@ snapshots: - react-native - utf-8-validate - '@trezor/blockchain-link-utils@1.2.0(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@mobily/ts-belt': 3.13.1 - '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@trezor/env-utils': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - utf-8-validate - - '@trezor/blockchain-link-utils@1.2.0(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@mobily/ts-belt': 3.13.1 - '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@trezor/env-utils': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - utf-8-validate - '@trezor/blockchain-link@2.3.0(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 @@ -19708,52 +19591,6 @@ snapshots: - supports-color - utf-8-validate - '@trezor/blockchain-link@2.3.0(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@trezor/blockchain-link-types': 1.2.0(bufferutil@4.0.8)(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/blockchain-link-utils': 1.2.0(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/utils': 9.2.0(tslib@2.7.0) - '@trezor/utxo-lib': 2.2.0(tslib@2.7.0) - '@types/web': 0.0.138 - events: 3.3.0 - ripple-lib: 1.10.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - socks-proxy-agent: 6.1.1 - tslib: 2.7.0 - ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - supports-color - - utf-8-validate - - '@trezor/blockchain-link@2.3.0(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.95.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@trezor/blockchain-link-types': 1.2.0(bufferutil@4.0.8)(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/blockchain-link-utils': 1.2.0(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/utils': 9.2.0(tslib@2.7.0) - '@trezor/utxo-lib': 2.2.0(tslib@2.7.0) - '@types/web': 0.0.138 - events: 3.3.0 - ripple-lib: 1.10.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - socks-proxy-agent: 6.1.1 - tslib: 2.7.0 - ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - supports-color - - utf-8-validate - '@trezor/connect-analytics@1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': dependencies: '@trezor/analytics': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) @@ -19763,24 +19600,6 @@ snapshots: - expo-localization - react-native - '@trezor/connect-analytics@1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': - dependencies: - '@trezor/analytics': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - expo-constants - - expo-localization - - react-native - - '@trezor/connect-analytics@1.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': - dependencies: - '@trezor/analytics': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - expo-constants - - expo-localization - - react-native - '@trezor/connect-common@0.2.0(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': dependencies: '@trezor/env-utils': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) @@ -19791,26 +19610,6 @@ snapshots: - expo-localization - react-native - '@trezor/connect-common@0.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': - dependencies: - '@trezor/env-utils': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - expo-constants - - expo-localization - - react-native - - '@trezor/connect-common@0.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': - dependencies: - '@trezor/env-utils': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - expo-constants - - expo-localization - - react-native - '@trezor/connect-web@9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': dependencies: '@trezor/connect': 9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) @@ -19827,38 +19626,6 @@ snapshots: - supports-color - utf-8-validate - '@trezor/connect-web@9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@trezor/connect': 9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/connect-common': 0.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - '@babel/core' - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - supports-color - - utf-8-validate - - '@trezor/connect-web@9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@trezor/connect': 9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/connect-common': 0.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - tslib: 2.7.0 - transitivePeerDependencies: - - '@babel/core' - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - supports-color - - utf-8-validate - '@trezor/connect@9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': dependencies: '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2) @@ -19890,68 +19657,6 @@ snapshots: - supports-color - utf-8-validate - '@trezor/connect@9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2) - '@ethereumjs/common': 4.4.0 - '@ethereumjs/tx': 5.4.0 - '@fivebinaries/coin-selection': 2.2.1 - '@trezor/blockchain-link': 2.3.0(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/blockchain-link-types': 1.2.0(bufferutil@4.0.8)(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/connect-analytics': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/connect-common': 0.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/protobuf': 1.2.0(tslib@2.7.0) - '@trezor/protocol': 1.2.0(tslib@2.7.0) - '@trezor/schema-utils': 1.2.0(tslib@2.7.0) - '@trezor/transport': 1.3.0(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - '@trezor/utxo-lib': 2.2.0(tslib@2.7.0) - blakejs: 1.2.1 - bs58: 5.0.0 - bs58check: 3.0.1 - cross-fetch: 4.0.0 - tslib: 2.7.0 - transitivePeerDependencies: - - '@babel/core' - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - supports-color - - utf-8-validate - - '@trezor/connect@9.4.0(@babel/core@7.25.2)(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10)': - dependencies: - '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2) - '@ethereumjs/common': 4.4.0 - '@ethereumjs/tx': 5.4.0 - '@fivebinaries/coin-selection': 2.2.1 - '@trezor/blockchain-link': 2.3.0(bufferutil@4.0.8)(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/blockchain-link-types': 1.2.0(bufferutil@4.0.8)(tslib@2.7.0)(utf-8-validate@5.0.10) - '@trezor/connect-analytics': 1.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/connect-common': 0.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0) - '@trezor/protobuf': 1.2.0(tslib@2.7.0) - '@trezor/protocol': 1.2.0(tslib@2.7.0) - '@trezor/schema-utils': 1.2.0(tslib@2.7.0) - '@trezor/transport': 1.3.0(tslib@2.7.0) - '@trezor/utils': 9.2.0(tslib@2.7.0) - '@trezor/utxo-lib': 2.2.0(tslib@2.7.0) - blakejs: 1.2.1 - bs58: 5.0.0 - bs58check: 3.0.1 - cross-fetch: 4.0.0 - tslib: 2.7.0 - transitivePeerDependencies: - - '@babel/core' - - bufferutil - - encoding - - expo-constants - - expo-localization - - react-native - - supports-color - - utf-8-validate - '@trezor/env-utils@1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': dependencies: tslib: 2.7.0 @@ -19959,20 +19664,6 @@ snapshots: optionalDependencies: react-native: 0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10) - '@trezor/env-utils@1.2.0(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': - dependencies: - tslib: 2.7.0 - ua-parser-js: 1.0.39 - optionalDependencies: - react-native: 0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10) - - '@trezor/env-utils@1.2.0(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10))(tslib@2.7.0)': - dependencies: - tslib: 2.7.0 - ua-parser-js: 1.0.39 - optionalDependencies: - react-native: 0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10) - '@trezor/protobuf@1.2.0(tslib@2.7.0)': dependencies: '@trezor/schema-utils': 1.2.0(tslib@2.7.0) @@ -20030,6 +19721,12 @@ snapshots: varuint-bitcoin: 1.1.2 wif: 4.0.0 + '@types/axios-mock-adapter@1.10.0(axios@1.7.2)': + dependencies: + axios-mock-adapter: 2.0.0(axios@1.7.2) + transitivePeerDependencies: + - axios + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.25.6 @@ -20424,7 +20121,7 @@ snapshots: vite: 5.2.9(@types/node@20.11.5)(terser@5.33.0) vue: 3.4.3(typescript@5.3.3) - '@vitest/coverage-v8@1.1.2(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0))': + '@vitest/coverage-v8@1.1.2(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -20443,7 +20140,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.33.0))': + '@vitest/coverage-v8@2.0.5(vitest@2.0.5(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -21679,6 +21376,12 @@ snapshots: axe-core@4.10.0: {} + axios-mock-adapter@2.0.0(axios@1.7.2): + dependencies: + axios: 1.7.2 + fast-deep-equal: 3.1.3 + is-buffer: 2.0.5 + axios@1.6.7: dependencies: follow-redirects: 1.15.9 @@ -22522,6 +22225,8 @@ snapshots: dependencies: '@babel/runtime': 7.25.6 + date-fns@4.1.0: {} + dayjs@1.11.10: {} debug@2.6.9: @@ -23123,8 +22828,8 @@ snapshots: '@typescript-eslint/parser': 6.18.1(eslint@8.57.0)(typescript@5.3.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.0) eslint-plugin-react: 7.36.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -23166,19 +22871,19 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node @@ -23196,14 +22901,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.11.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 6.18.1(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.18.1(eslint@8.56.0)(typescript@5.3.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -23235,7 +22940,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -23246,7 +22951,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -23257,7 +22962,7 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 6.18.1(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.18.1(eslint@8.56.0)(typescript@5.3.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -24433,6 +24138,8 @@ snapshots: call-bind: 1.0.7 has-tostringtag: 1.0.2 + is-buffer@2.0.5: {} + is-bun-module@1.2.1: dependencies: semver: 7.6.3 @@ -26583,111 +26290,6 @@ snapshots: - typescript - utf-8-validate - react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10): - dependencies: - '@jest/create-cache-key-function': 29.7.0 - '@react-native-community/cli': 14.1.0(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10) - '@react-native-community/cli-platform-android': 14.1.0 - '@react-native-community/cli-platform-ios': 14.1.0 - '@react-native/assets-registry': 0.75.3 - '@react-native/codegen': 0.75.3(@babel/preset-env@7.25.4(@babel/core@7.25.2)) - '@react-native/community-cli-plugin': 0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@react-native/gradle-plugin': 0.75.3 - '@react-native/js-polyfills': 0.75.3 - '@react-native/normalize-colors': 0.75.3 - '@react-native/virtualized-lists': 0.75.3(@types/react@18.2.62)(react-native@0.75.3(@babel/core@7.25.2)(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0) - abort-controller: 3.0.0 - anser: 1.4.10 - ansi-regex: 5.0.1 - base64-js: 1.5.1 - chalk: 4.1.2 - commander: 9.5.0 - event-target-shim: 5.0.1 - flow-enums-runtime: 0.0.6 - glob: 7.2.3 - invariant: 2.2.4 - jest-environment-node: 29.7.0 - jsc-android: 250231.0.0 - memoize-one: 5.2.1 - metro-runtime: 0.80.12 - metro-source-map: 0.80.12 - mkdirp: 0.5.6 - nullthrows: 1.1.1 - pretty-format: 26.6.2 - promise: 8.3.0 - react: 18.2.0 - react-devtools-core: 5.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - react-refresh: 0.14.2 - regenerator-runtime: 0.13.11 - scheduler: 0.24.0-canary-efb381bbf-20230505 - semver: 7.6.3 - stacktrace-parser: 0.1.10 - whatwg-fetch: 3.6.20 - ws: 6.2.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) - yargs: 17.7.2 - optionalDependencies: - '@types/react': 18.2.62 - transitivePeerDependencies: - - '@babel/core' - - '@babel/preset-env' - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - optional: true - - react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10): - dependencies: - '@jest/create-cache-key-function': 29.7.0 - '@react-native-community/cli': 14.1.0(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10) - '@react-native-community/cli-platform-android': 14.1.0 - '@react-native-community/cli-platform-ios': 14.1.0 - '@react-native/assets-registry': 0.75.3 - '@react-native/codegen': 0.75.3(@babel/preset-env@7.25.4(@babel/core@7.25.2)) - '@react-native/community-cli-plugin': 0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@react-native/gradle-plugin': 0.75.3 - '@react-native/js-polyfills': 0.75.3 - '@react-native/normalize-colors': 0.75.3 - '@react-native/virtualized-lists': 0.75.3(react-native@0.75.3(@babel/core@7.25.2)(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)) - abort-controller: 3.0.0 - anser: 1.4.10 - ansi-regex: 5.0.1 - base64-js: 1.5.1 - chalk: 4.1.2 - commander: 9.5.0 - event-target-shim: 5.0.1 - flow-enums-runtime: 0.0.6 - glob: 7.2.3 - invariant: 2.2.4 - jest-environment-node: 29.7.0 - jsc-android: 250231.0.0 - memoize-one: 5.2.1 - metro-runtime: 0.80.12 - metro-source-map: 0.80.12 - mkdirp: 0.5.6 - nullthrows: 1.1.1 - pretty-format: 26.6.2 - promise: 8.3.0 - react-devtools-core: 5.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - react-refresh: 0.14.2 - regenerator-runtime: 0.13.11 - scheduler: 0.24.0-canary-efb381bbf-20230505 - semver: 7.6.3 - stacktrace-parser: 0.1.10 - whatwg-fetch: 3.6.20 - ws: 6.2.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) - yargs: 17.7.2 - transitivePeerDependencies: - - '@babel/core' - - '@babel/preset-env' - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - optional: true - react-qr-reader@2.2.1(react-dom@16.13.1(react@16.13.1))(react@16.13.1): dependencies: jsqr: 1.4.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e9a6f6d14b..b681327b04 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,6 +14,7 @@ packages: - 'packages/ethers5' - 'packages/solana' - 'packages/cdn' + - 'packages/experimental' - 'apps/*' - 'examples/*' - 'services/*'