diff --git a/apps/console/src/components/sections/code/CodeSection/index.tsx b/apps/console/src/components/sections/code/CodeSection/index.tsx index 82ca795f..4ca36802 100644 --- a/apps/console/src/components/sections/code/CodeSection/index.tsx +++ b/apps/console/src/components/sections/code/CodeSection/index.tsx @@ -190,7 +190,7 @@ export default App;`.trim(); } ${poapPluginEnabled ? '@justweb3/poap-plugin' : ''} ${ efpPluginEnabled ? '@justweb3/efp-plugin' : '' } ${dentityPluginEnabled ? '@justweb3/dentity-plugin' : ''} - @justweb3/widget viem wagmi @rainbow-me/rainbowkit @tanstack/react-query ethers`; + @justweb3/widget viem wagmi @rainbow-me/rainbowkit @tanstack/react-query`; }, [ dentityPluginEnabled, efpPluginEnabled, diff --git a/apps/console/tsconfig.json b/apps/console/tsconfig.json index 5aabca4b..7845c4a4 100644 --- a/apps/console/tsconfig.json +++ b/apps/console/tsconfig.json @@ -11,6 +11,12 @@ "resolveJsonModule": true, "isolatedModules": true, "incremental": true, + "target": "ESNext", + "downlevelIteration": true, + "skipLibCheck": true, + "composite": false, + "module": "ESNext", + "moduleResolution": "Bundler", "plugins": [ { "name": "next" @@ -41,5 +47,11 @@ ".next/types/**/*.ts", "../../dist/dist/apps/console/types/**/*.ts" ], - "exclude": ["node_modules", "jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] + "exclude": [ + "node_modules", + "**/node_modules/**", + "jest.config.ts", + "**/*.spec.ts", + "**/*.test.ts" + ] } diff --git a/package.json b/package.json index c333d219..d488bb73 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "cropperjs": "1.6.2", "dotenv": "16.4.5", "embla-carousel-react": "8.3.0", - "ethers": "6.11.1", "express": "4.18.1", "express-session": "1.18.0", "input-otp": "1.2.4", @@ -83,7 +82,7 @@ "tailwindcss-animate": "1.0.7", "tslib": "2.3.0", "vaul": "1.1.1", - "viem": "^2.35.0", + "viem": "^2.48.0", "vite-plugin-dts": "3.7.3", "wagmi": "2.14.16" }, diff --git a/packages/@justaname.id/react/package.json b/packages/@justaname.id/react/package.json index 4a35e6e8..7eab989b 100644 --- a/packages/@justaname.id/react/package.json +++ b/packages/@justaname.id/react/package.json @@ -9,9 +9,8 @@ }, "peerDependencies": { "@tanstack/react-query": "^5.x", - "ethers": "^5.6.8 || ^6.0.8", "react": ">=17", - "viem": "2.x", + "viem": "^2.48.0", "wagmi": "2.x" }, "devDependencies": { diff --git a/packages/@justaname.id/react/src/lib/helpers/ethersCompat.ts b/packages/@justaname.id/react/src/lib/helpers/ethersCompat.ts deleted file mode 100644 index 682e9f25..00000000 --- a/packages/@justaname.id/react/src/lib/helpers/ethersCompat.ts +++ /dev/null @@ -1,51 +0,0 @@ -// inspired by spruceid siwe: https://github.com/spruceid/siwe/blob/main/packages/siwe/lib/ethersCompat.ts - -import { ethers } from 'ethers'; - -// @ts-expect-error -- compatibility hack -type ProviderV5 = ethers.providers.Provider; -type ProviderV6 = ethers.Provider; -// @ts-expect-error -- compatibility hack -type JsonRpcProviderV5 = ethers.providers.JsonRpcProvider; -type JsonRpcProviderV6 = ethers.JsonRpcProvider; - -export type Provider = ProviderV6 extends undefined ? ProviderV5 : ProviderV6; -export type JsonRpcProvider = JsonRpcProviderV6 extends undefined - ? JsonRpcProviderV5 - : JsonRpcProviderV6; - -interface EthersCompat { - namehash?: (name: string) => string; - getAddress?: (address: string) => string; - JsonRpcProvider?: new (...args: any[]) => JsonRpcProvider; - utils: { - namehash: (name: string) => string; - getAddress: (address: string) => string; - }; - providers: { - JsonRpcProvider: new (...args: any[]) => JsonRpcProvider; - }; -} - -const ethersCompat = ethers as unknown as EthersCompat; - -export const getJsonRpcProvider = ( - providerUrl?: string, - chainId?: number -): JsonRpcProvider => { - if ('JsonRpcProvider' in ethersCompat) { - return new ethersCompat.JsonRpcProvider!(providerUrl, chainId); - } else { - return new ethersCompat.providers.JsonRpcProvider(providerUrl, chainId); - } -}; - -export const namehash: (name: string) => string = - 'namehash' in ethersCompat - ? ethersCompat.namehash! - : ethersCompat.utils.namehash; - -export const getAddress: (address: string) => string = - 'getAddress' in ethersCompat - ? ethersCompat.getAddress! - : ethersCompat.utils.getAddress; diff --git a/packages/@justaname.id/react/src/lib/helpers/resolveDefaultChain.spec.ts b/packages/@justaname.id/react/src/lib/helpers/resolveDefaultChain.spec.ts new file mode 100644 index 00000000..9ae8f2f1 --- /dev/null +++ b/packages/@justaname.id/react/src/lib/helpers/resolveDefaultChain.spec.ts @@ -0,0 +1,24 @@ +import { resolveDefaultChain } from './resolveDefaultChain'; + +describe('resolveDefaultChain', () => { + it('returns undefined when chainId is undefined (no wallet connected)', () => { + // Regression: previously `!chainId === undefined` was always false, causing + // an undefined chainId to silently fall through to mainnet — which then + // locked the SDK into mainnet config when the user intended Sepolia. + expect(resolveDefaultChain(undefined)).toBeUndefined(); + }); + + it('passes through mainnet (1)', () => { + expect(resolveDefaultChain(1)).toBe(1); + }); + + it('passes through Sepolia (11155111)', () => { + expect(resolveDefaultChain(11155111)).toBe(11155111); + }); + + it('falls back to mainnet for unsupported chains', () => { + expect(resolveDefaultChain(137)).toBe(1); + expect(resolveDefaultChain(10)).toBe(1); + expect(resolveDefaultChain(42161)).toBe(1); + }); +}); diff --git a/packages/@justaname.id/react/src/lib/helpers/resolveDefaultChain.ts b/packages/@justaname.id/react/src/lib/helpers/resolveDefaultChain.ts new file mode 100644 index 00000000..9f1ae1f7 --- /dev/null +++ b/packages/@justaname.id/react/src/lib/helpers/resolveDefaultChain.ts @@ -0,0 +1,18 @@ +import { ChainId } from '@justaname.id/sdk'; + +/** + * Resolves the wagmi-provided chainId to a supported JustaName ChainId. + * + * - Returns `undefined` when no chain is available (wallet not connected yet); + * callers should defer SDK initialization until a chain resolves. + * - Returns `1` (mainnet) for any chainId that is not one of the two + * officially supported networks (1, 11155111). + * - Otherwise passes the chainId through unchanged. + */ +export const resolveDefaultChain = ( + chainId: number | undefined +): ChainId | undefined => { + if (chainId === undefined) return undefined; + if (chainId !== 1 && chainId !== 11155111) return 1; + return chainId; +}; diff --git a/packages/@justaname.id/react/src/lib/hooks/index.ts b/packages/@justaname.id/react/src/lib/hooks/index.ts index 2bca5d6f..d91734d8 100644 --- a/packages/@justaname.id/react/src/lib/hooks/index.ts +++ b/packages/@justaname.id/react/src/lib/hooks/index.ts @@ -1,6 +1,5 @@ export * from './account'; export * from './ens'; -export * from './mApp'; export * from './uploadMedia'; export * from './records'; export * from './resolver'; diff --git a/packages/@justaname.id/react/src/lib/hooks/mApp/index.ts b/packages/@justaname.id/react/src/lib/hooks/mApp/index.ts deleted file mode 100644 index 65858aa0..00000000 --- a/packages/@justaname.id/react/src/lib/hooks/mApp/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './useAddMAppPermission'; -export * from './useRevokeMAppPermission'; -export * from './useIsMAppEnabled'; -export * from './useCanEnableMApps'; -export * from './useEnabledMApps'; \ No newline at end of file diff --git a/packages/@justaname.id/react/src/lib/hooks/mApp/useAddMAppPermission.ts b/packages/@justaname.id/react/src/lib/hooks/mApp/useAddMAppPermission.ts deleted file mode 100644 index f629a833..00000000 --- a/packages/@justaname.id/react/src/lib/hooks/mApp/useAddMAppPermission.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { UseMutateAsyncFunction, useMutation } from '@tanstack/react-query'; -import { useJustaName } from '../../providers'; -import { ChainId, RequestAddMAppPermissionChallengeRoute, AddMAppPermissionRoute } from '@justaname.id/sdk'; -import { useSignMessage } from 'wagmi'; -import { useAccountSubnames, useMountedAccount } from '../account'; -import { useRecords } from '../records'; -import { useMemo } from 'react'; - -export interface UseAddMAppPermissionFunctionParams extends Omit { - mApp?: string -} - -export interface UseAddMAppPermissionParams extends Omit { - mApp: string - chainId?: ChainId -} - -export interface UseRequestAddMAppPermission { - addMAppPermission: UseMutateAsyncFunction, - isAddMAppPermissionPending: boolean; -} - - -export const useAddMAppPermission = (params: UseAddMAppPermissionParams): UseRequestAddMAppPermission => { - const { justaname, chainId } = useJustaName() - const { signMessageAsync } = useSignMessage() - const { address} = useMountedAccount() - const { getRecords } = useRecords() - const _chainId = useMemo(() => params.chainId || chainId, [params.chainId, chainId]) - - const { refetchAccountSubnames } = useAccountSubnames() - const mutate = useMutation({ - mutationFn: async ( - _params: UseAddMAppPermissionFunctionParams - ) => { - if (!address) { - throw new Error('Wallet not connected') - } - - const challengeResponse = await justaname.mApps.requestAddMAppPermissionChallenge({ - subname: _params.subname, - address: address, - mApp: _params?.mApp || params.mApp, - chainId: _params?.chainId || _chainId - }) - - const signature = await signMessageAsync({ - message: challengeResponse.challenge, - account: address - }) - - const response = await justaname.mApps.addMAppPermission({ - message: challengeResponse.challenge, - address: address, - signature, - }) - - refetchAccountSubnames() - - getRecords({ - ens: _params.subname, - chainId: _chainId, - }, true) - return response - } - }) - - return { - addMAppPermission: mutate.mutateAsync, - isAddMAppPermissionPending: mutate.isPending - } -} \ No newline at end of file diff --git a/packages/@justaname.id/react/src/lib/hooks/mApp/useCanEnableMApps.ts b/packages/@justaname.id/react/src/lib/hooks/mApp/useCanEnableMApps.ts deleted file mode 100644 index 3885e8bd..00000000 --- a/packages/@justaname.id/react/src/lib/hooks/mApp/useCanEnableMApps.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { ChainId } from '@justaname.id/sdk'; -import { useQuery } from '@tanstack/react-query'; -import { useJustaName } from '../../providers'; -import { useRecords } from '../records'; -import { useMemo } from 'react'; -import { defaultOptions } from '../../query'; - -export const buildCanEnableMAppsKey = ( - ens: string, - chainId: ChainId | undefined -) => ['CAN_ENABLE_MAPPS', ens, chainId]; - -export interface UseCanEnableMAppsParams { - ens: string; - chainId?: ChainId; -} - -export interface UseCanEnableMAppsResult { - canEnableMApps: boolean | undefined; - isCanEnableMAppsPending: boolean; - refetchCanEnableMApps: () => void; -} - -export const useCanEnableMApps = ( - params: UseCanEnableMAppsParams -): UseCanEnableMAppsResult => { - const { chainId } = useJustaName(); - const _chainId = useMemo( - () => params.chainId || chainId, - [params.chainId, chainId] - ); - const { records } = useRecords({ - ens: params.ens, - chainId: _chainId, - }); - const query = useQuery({ - ...defaultOptions, - queryKey: buildCanEnableMAppsKey(params.ens, _chainId), - queryFn: () => { - return records?.isJAN; - }, - enabled: Boolean(params.ens) && Boolean(_chainId) && Boolean(records), - }); - - return { - canEnableMApps: query.data, - refetchCanEnableMApps: query.refetch, - isCanEnableMAppsPending: query.isPending, - }; -}; diff --git a/packages/@justaname.id/react/src/lib/hooks/mApp/useEnabledMApps.ts b/packages/@justaname.id/react/src/lib/hooks/mApp/useEnabledMApps.ts deleted file mode 100644 index e029eb95..00000000 --- a/packages/@justaname.id/react/src/lib/hooks/mApp/useEnabledMApps.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { ChainId } from '@justaname.id/sdk'; -import { useQuery } from '@tanstack/react-query'; -import { useJustaName } from '../../providers'; -import { useRecords } from '../records'; -import { useEffect, useMemo } from 'react'; -import { defaultOptions } from '../../query'; - -export const buildEnabledMAppsKey = ( - ens: string, - chainId: ChainId | undefined -) => ['ENABLED_MAPPS', ens, chainId]; - -export interface UseEnabledMAppsParams { - ens: string; - chainId?: ChainId; - providerUrl?: string; -} - -export interface UseEnabledMAppsResult { - enabledMApps: string[] | undefined; - refetchEnabledMApps: () => void; - isMAppEnabledPending: boolean; -} - -export const useEnabledMApps = ( - params: UseEnabledMAppsParams -): UseEnabledMAppsResult => { - const { justaname, chainId } = useJustaName(); - const _chainId = useMemo( - () => params.chainId || chainId, - [params.chainId, chainId] - ); - const { records } = useRecords({ - ens: params.ens, - chainId: _chainId, - }); - - const query = useQuery({ - ...defaultOptions, - queryKey: buildEnabledMAppsKey(params.ens, _chainId), - queryFn: async () => { - if (!records) { - return; - } - if (!records.isJAN) { - return false; - } - const mAppField = records.records.texts.find( - (text) => text.key === 'mApps' - ); - return mAppField ? JSON.parse(mAppField.value).mApps : []; - }, - enabled: - Boolean(params.ens) && - Boolean(justaname) && - Boolean(params.ens.length > 0) && - Boolean(_chainId) && - Boolean(records), - }); - - useEffect(() => { - if (records) query.refetch(); - }, [records]); - - return { - enabledMApps: query.data, - refetchEnabledMApps: query.refetch, - isMAppEnabledPending: query.isPending, - }; -}; diff --git a/packages/@justaname.id/react/src/lib/hooks/mApp/useIsMAppEnabled.ts b/packages/@justaname.id/react/src/lib/hooks/mApp/useIsMAppEnabled.ts deleted file mode 100644 index a81b3a78..00000000 --- a/packages/@justaname.id/react/src/lib/hooks/mApp/useIsMAppEnabled.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ChainId } from '@justaname.id/sdk'; -import { useQuery } from '@tanstack/react-query'; -import { useJustaName } from '../../providers'; -import { useRecords } from '../records'; -import { useEffect, useMemo } from 'react'; -import { defaultOptions } from '../../query'; - -export const buildIsMAppEnabledKey = ( - ens: string, - mApp: string, - chainId: ChainId | undefined -) => ['IS_MAPP_ENABLED', ens, mApp, chainId]; - -export interface UseIsMAppEnabledParams { - ens: string; - mApp: string; - chainId?: ChainId; -} - -export interface UseIsMAppEnabledResult { - isMAppEnabled: boolean | undefined; - isMAppEnabledPending: boolean; - refetchIsMAppEnabled: () => void; -} - -export const useIsMAppEnabled = ( - params: UseIsMAppEnabledParams -): UseIsMAppEnabledResult => { - const { justaname, chainId } = useJustaName(); - const _chainId = useMemo( - () => params.chainId || chainId, - [params.chainId, chainId] - ); - const { records } = useRecords({ - ens: params.ens, - chainId: _chainId, - }); - - const query = useQuery({ - ...defaultOptions, - queryKey: buildIsMAppEnabledKey(params.ens, params.mApp, _chainId), - queryFn: async () => { - if (!records) { - return false; - } - if (!records.isJAN) { - return false; - } - const mAppField = records.records.texts.find( - (text) => text.key === 'mApps' - ); - if (!mAppField) { - return false; - } - const mAppFieldValue = JSON.parse(mAppField.value); - if (!mAppFieldValue) { - return false; - } - return mAppFieldValue.mApps.includes(params?.mApp); - }, - enabled: - Boolean(params.ens) && - Boolean(justaname) && - params.ens.length > 0 && - params?.mApp?.length > 0 && - Boolean(_chainId) && - Boolean(records), - }); - - useEffect(() => { - query.refetch(); - }, [records]); - - return { - isMAppEnabled: query.data, - refetchIsMAppEnabled: query.refetch, - isMAppEnabledPending: query.isPending, - }; -}; diff --git a/packages/@justaname.id/react/src/lib/hooks/mApp/useRevokeMAppPermission.ts b/packages/@justaname.id/react/src/lib/hooks/mApp/useRevokeMAppPermission.ts deleted file mode 100644 index 3813b542..00000000 --- a/packages/@justaname.id/react/src/lib/hooks/mApp/useRevokeMAppPermission.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { UseMutateAsyncFunction, useMutation } from '@tanstack/react-query'; -import { useJustaName } from '../../providers'; -import { ChainId, RevokeMAppPermissionRoute } from '@justaname.id/sdk'; -import { useSignMessage } from 'wagmi'; -import { useAccountSubnames, useMountedAccount } from '../account'; -import { useRecords } from '../records'; -import { useMemo } from 'react'; - -export interface UseRequestRevokeMAppPermissionResult { - revokeMAppPermission: UseMutateAsyncFunction; - isRevokeMAppPermissionPending: boolean; -} - -export interface UseRevokeMAppPermissionFunctionParams { - ens: string -} - -export interface UseRevokeMAppPermissionParams { - mApp: string, - chainId?: ChainId, - providerUrl?: string -} - -export const useRevokeMAppPermission = (params: UseRevokeMAppPermissionParams): UseRequestRevokeMAppPermissionResult => { - const { justaname, chainId } = useJustaName() - const _chainId = useMemo(() => params.chainId || chainId, [params.chainId, chainId]) - const { signMessageAsync } = useSignMessage() - const { address} = useMountedAccount() - const { refetchAccountSubnames } = useAccountSubnames() - const { getRecords } = useRecords() - const mutate = useMutation({ - mutationFn: async ( - _params:UseRevokeMAppPermissionFunctionParams - ) => { - if (!address) { - throw new Error('Wallet not connected') - } - const challengeResponse = await justaname.mApps.requestRevokeMAppPermissionChallenge({ - subname: _params.ens, - address: address, - mApp: params.mApp, - chainId: _chainId - }) - - const signature = await signMessageAsync({ - message: challengeResponse.challenge, - account: address - }) - - const response = await justaname.mApps.revokeMAppPermission({ - message: challengeResponse.challenge, - address: address, - signature - }) - - refetchAccountSubnames() - getRecords({ - ens: _params.ens, - chainId: _chainId, - }, true) - - return response - } - }) - - return { - revokeMAppPermission: mutate.mutateAsync, - isRevokeMAppPermissionPending: mutate.isPending - } -} \ No newline at end of file diff --git a/packages/@justaname.id/react/src/lib/hooks/resolver/useSetNameHashJustaNameResolver.ts b/packages/@justaname.id/react/src/lib/hooks/resolver/useSetNameHashJustaNameResolver.ts index f5ddaf4f..14300a9d 100644 --- a/packages/@justaname.id/react/src/lib/hooks/resolver/useSetNameHashJustaNameResolver.ts +++ b/packages/@justaname.id/react/src/lib/hooks/resolver/useSetNameHashJustaNameResolver.ts @@ -10,7 +10,8 @@ import { } from 'wagmi'; import { useOffchainResolvers } from '../offchainResolver/useOffchainResolvers'; import { useMountedAccount } from '../account/useMountedAccount'; -import { getAddress, namehash } from '../../helpers/ethersCompat'; +import { getAddress } from 'viem'; +import { namehash } from 'viem/ens'; const ZeroAddress = '0x0000000000000000000000000000000000000000'; diff --git a/packages/@justaname.id/react/src/lib/hooks/subname/useAddSubname.ts b/packages/@justaname.id/react/src/lib/hooks/subname/useAddSubname.ts index 84acd5b6..8f45c37e 100644 --- a/packages/@justaname.id/react/src/lib/hooks/subname/useAddSubname.ts +++ b/packages/@justaname.id/react/src/lib/hooks/subname/useAddSubname.ts @@ -5,7 +5,7 @@ import { useJustaName, useSubnameSignature } from '../../providers'; import { useMountedAccount } from '../account/useMountedAccount'; import { sanitizeRecords, SubnameAddRoute } from '@justaname.id/sdk'; import { useAccountSubnames } from '../account/useAccountSubnames'; -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { Records } from '../../types'; export type UseAddSubnameFunctionParams = SubnameAddRoute['params']; @@ -28,7 +28,8 @@ export interface UseAddSubnameResult { export const useAddSubname = ( params?: UseAddSubnameParams ): UseAddSubnameResult => { - const { justaname, backendUrl, routes, chainId, ensDomains } = useJustaName(); + const { justaname, backendUrl, routes, chainId, ensDomains, dev } = + useJustaName(); const { address } = useMountedAccount(); const { getSignature } = useSubnameSignature(); const { refetchAccountSubnames } = useAccountSubnames(); @@ -42,6 +43,7 @@ export const useAddSubname = ( ensDomains.find((ensDomain) => ensDomain.chainId === _chainId)?.ensDomain, [params?.ensDomain, ensDomains, _chainId] ); + const _backendUrl = useMemo( () => params?.backendUrl || backendUrl, [params?.backendUrl, backendUrl] @@ -58,6 +60,18 @@ export const useAddSubname = ( params?.apiKey || ensDomains.find((ensDomain) => ensDomain.chainId === _chainId)?.apiKey; + useEffect(() => { + if (dev) { + // eslint-disable-next-line no-console + console.debug( + '[JustaName] useAddSubname resolved chainId:', + _chainId, + 'ensDomain:', + _ensDomain + ); + } + }, [dev, _chainId, _ensDomain]); + const mutate = useMutation({ mutationFn: async (_params: UseAddSubnameFunctionParams) => { if (!address) { diff --git a/packages/@justaname.id/react/src/lib/providers/JustaNameProvider.tsx b/packages/@justaname.id/react/src/lib/providers/JustaNameProvider.tsx index b3a690a3..8a506171 100644 --- a/packages/@justaname.id/react/src/lib/providers/JustaNameProvider.tsx +++ b/packages/@justaname.id/react/src/lib/providers/JustaNameProvider.tsx @@ -19,6 +19,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { defaultRoutes } from '../constants/default-routes'; import { useMountedAccount } from '../hooks/account/useMountedAccount'; import { useSignMessage } from 'wagmi'; +import { resolveDefaultChain } from '../helpers/resolveDefaultChain'; export type JustaNameConfigWithoutDefaultChainId = Omit< JustaNameConfig, @@ -32,7 +33,7 @@ export interface JustaNameContextProps justaname: JustaName; routes: typeof defaultRoutes; backendUrl: string; - selectedNetwork: NetworkWithProvider; + selectedNetwork: NetworkWithProvider | undefined; selectedEnsDomain: string | undefined; chainId: ChainId | undefined; } @@ -67,13 +68,20 @@ export const JustaNameProvider: FC = ({ }) => { const { chainId } = useMountedAccount(); - const defaultChain = useMemo(() => { - return !chainId === undefined - ? 1 - : chainId !== 1 && chainId !== 11155111 - ? 1 - : chainId; - }, [chainId]); + const defaultChain = useMemo(() => resolveDefaultChain(chainId), [chainId]); + + useEffect(() => { + if (initialConfig.dev) { + // eslint-disable-next-line no-console + console.debug( + '[JustaName] defaultChain resolved:', + defaultChain, + '(wagmi chainId:', + chainId, + ')' + ); + } + }, [defaultChain, chainId, initialConfig.dev]); // const [config, setConfig] = useState(initialConfig); const config: JustaNameProviderConfig = useMemo( @@ -104,10 +112,10 @@ export const JustaNameProvider: FC = ({ return JustaName.createNetworks(justanameConfig.networks); }, [justanameConfig.networks]); - const selectedNetwork = useMemo(() => { + const selectedNetwork = useMemo(() => { return configuredNetworks.find( (network) => network.chainId === defaultChain - ) as NetworkWithProvider; + ); }, [configuredNetworks, defaultChain]); return ( diff --git a/packages/@justaname.id/sdk/package.json b/packages/@justaname.id/sdk/package.json index f1c1be87..721d209b 100644 --- a/packages/@justaname.id/sdk/package.json +++ b/packages/@justaname.id/sdk/package.json @@ -10,9 +10,8 @@ "jest": "^29.4.1" }, "peerDependencies": { - "ethers": "^5.6.8 || ^6.0.8", "siwe": ">=2.0.0", - "viem": ">=2.35.0" + "viem": "^2.48.0" }, "exports": { "./package.json": "./dist/package.json", diff --git a/packages/@justaname.id/sdk/src/lib/api/rest.ts b/packages/@justaname.id/sdk/src/lib/api/rest.ts index 49cef6e0..f611090b 100644 --- a/packages/@justaname.id/sdk/src/lib/api/rest.ts +++ b/packages/@justaname.id/sdk/src/lib/api/rest.ts @@ -63,6 +63,17 @@ export const restCall = < } } + if (dev) { + // Log only the route and the non-sensitive top-level keys present on the + // payload. We avoid dumping the full body because it can contain SIWE + // messages, addresses, and other PII. + const payload = (request ?? {}) as Record; + const keys = Object.keys(payload); + const chainId = (payload as { chainId?: unknown }).chainId; + // eslint-disable-next-line no-console + console.debug('[JustaName]', method, Routes[route], { chainId, keys }); + } + return controlledAxiosPromise( justANameInstance(dev).request({ url: Routes[route], diff --git a/packages/@justaname.id/sdk/src/lib/api/routes/index.ts b/packages/@justaname.id/sdk/src/lib/api/routes/index.ts index 1c6060f9..9cd6868a 100644 --- a/packages/@justaname.id/sdk/src/lib/api/routes/index.ts +++ b/packages/@justaname.id/sdk/src/lib/api/routes/index.ts @@ -1,13 +1,7 @@ import { - AddMAppPermissionRoute, - AppendMAppFieldRoute, IsSubnameAvailableRoute, OffchainResolversGetAllRoute, - RequestAddMAppPermissionChallengeRoute, - RequestAppendMAppFieldChallengeRoute, RequestChallengeRoute, - RequestRevokeMAppPermissionChallengeRoute, - RevokeMAppPermissionRoute, SubnameAcceptRoute, SubnameAddRoute, SubnameGetAllByAddressRoute, @@ -39,17 +33,9 @@ import { UPDATE_SUBNAME_ROUTE, } from './subnames'; import { - SIWE_MAPP_ADD_PERMISSION_ROUTE, - SIWE_MAPP_APPEND_FIELD_ROUTE, - SIWE_MAPP_REVOKE_PERMISSION_ROUTE, SIWE_REQUEST_CHALLENGE_ROUTE, SIWE_VERIFY_MESSAGE_ROUTE, } from './siwe'; -import { - MAPP_ADD_PERMISSION_ROUTE, - MAPP_APPEND_FIELD_ROUTE, - MAPP_REVOKE_PERMISSION_ROUTE, -} from './mapp'; import { GET_ALL_OFFCHAIN_RESOLVERS_ROUTE } from './offchain-resolver'; import { GET_PRIMARY_NAME_BY_ADDRESS_ROUTE, @@ -63,12 +49,6 @@ import { export interface ROUTES { SIWE_VERIFY_MESSAGE_ROUTE: VerifyMessageRoute; SIWE_REQUEST_CHALLENGE_ROUTE: RequestChallengeRoute; - SIWE_MAPP_ADD_PERMISSION_ROUTE: RequestAddMAppPermissionChallengeRoute; - SIWE_MAPP_APPEND_FIELD_ROUTE: RequestAppendMAppFieldChallengeRoute; - SIWE_MAPP_REVOKE_PERMISSION_ROUTE: RequestRevokeMAppPermissionChallengeRoute; - MAPP_ADD_PERMISSION_ROUTE: AddMAppPermissionRoute; - MAPP_APPEND_FIELD_ROUTE: AppendMAppFieldRoute; - MAPP_REVOKE_PERMISSION_ROUTE: RevokeMAppPermissionRoute; ACCEPT_SUBNAME_ROUTE: SubnameAcceptRoute; RESERVE_SUBNAME_ROUTE: SubnameReserveRoute; ADD_SUBNAME_ROUTE: SubnameAddRoute; @@ -91,12 +71,6 @@ export interface ROUTES { export const Routes: Record = { SIWE_VERIFY_MESSAGE_ROUTE, SIWE_REQUEST_CHALLENGE_ROUTE, - SIWE_MAPP_ADD_PERMISSION_ROUTE, - SIWE_MAPP_APPEND_FIELD_ROUTE, - SIWE_MAPP_REVOKE_PERMISSION_ROUTE, - MAPP_ADD_PERMISSION_ROUTE, - MAPP_APPEND_FIELD_ROUTE, - MAPP_REVOKE_PERMISSION_ROUTE, ACCEPT_SUBNAME_ROUTE, RESERVE_SUBNAME_ROUTE, ADD_SUBNAME_ROUTE, diff --git a/packages/@justaname.id/sdk/src/lib/api/routes/mapp.ts b/packages/@justaname.id/sdk/src/lib/api/routes/mapp.ts deleted file mode 100644 index b5f37936..00000000 --- a/packages/@justaname.id/sdk/src/lib/api/routes/mapp.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { GLOBAL_PREFIX} from './prefix'; - -/** - * Prefix for all MAPP routes - */ -const MAPP_ROUTE = GLOBAL_PREFIX + '/ens/v1/mapp'; - -/** - * Routes for MAPP Add Permission - */ -export const MAPP_ADD_PERMISSION_ROUTE = MAPP_ROUTE + '/permission/add'; - -/** - * Routes for MAPP Append Field - */ -export const MAPP_APPEND_FIELD_ROUTE = MAPP_ROUTE + '/field/append'; - - -/** - * Routes for MAPP Revoke Permission - */ -export const MAPP_REVOKE_PERMISSION_ROUTE = MAPP_ROUTE + '/permission/revoke'; \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/api/routes/siwe.ts b/packages/@justaname.id/sdk/src/lib/api/routes/siwe.ts index 3c233bcb..b5a2ae05 100644 --- a/packages/@justaname.id/sdk/src/lib/api/routes/siwe.ts +++ b/packages/@justaname.id/sdk/src/lib/api/routes/siwe.ts @@ -14,18 +14,3 @@ export const SIWE_REQUEST_CHALLENGE_ROUTE = SIWE_BASE_ROUTE + '/request-challeng * Routes for SIWE Verify Message */ export const SIWE_VERIFY_MESSAGE_ROUTE = SIWE_BASE_ROUTE + '/verify-message'; - -/** - * Routes for SIWE Request Challenge to Add MAPP Permission - */ -export const SIWE_MAPP_ADD_PERMISSION_ROUTE = SIWE_BASE_ROUTE + '/mapp/add-permission'; - -/** - * Routes for SIWE Request Challenge to Append Field - */ -export const SIWE_MAPP_APPEND_FIELD_ROUTE = SIWE_BASE_ROUTE + '/mapp/append-field'; - -/** - * Routes for SIWE Request Challenge to Revoke Permission - */ -export const SIWE_MAPP_REVOKE_PERMISSION_ROUTE = SIWE_BASE_ROUTE + '/mapp/revoke-permission'; \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/features/index.ts b/packages/@justaname.id/sdk/src/lib/features/index.ts index b13eed13..a591573f 100644 --- a/packages/@justaname.id/sdk/src/lib/features/index.ts +++ b/packages/@justaname.id/sdk/src/lib/features/index.ts @@ -1,5 +1,4 @@ export * from './subname-challenge'; export * from './subnames'; export * from './offchain-resolvers'; -export * from './sign-in'; -export * from './mApps'; \ No newline at end of file +export * from './sign-in'; \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/features/mApps/index.ts b/packages/@justaname.id/sdk/src/lib/features/mApps/index.ts deleted file mode 100644 index b8e3ac25..00000000 --- a/packages/@justaname.id/sdk/src/lib/features/mApps/index.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - AddMAppPermissionRoute, - AppendMAppFieldRoute, - ChainId, - MApp, - NetworksWithProvider, - RequestAddMAppPermissionChallengeRoute, - RequestAppendMAppFieldChallengeRoute, - RequestRevokeMAppPermissionChallengeRoute, - RevokeMAppPermissionRoute, - SiweConfig -} from '../../types'; -import { assertRestCall } from '../../api/rest'; -import { Subnames } from '../subnames'; - -export interface MAppsParams { - siweConfig?: Omit; - chainId: ChainId; - networks: NetworksWithProvider - subnames: Subnames; - dev: boolean; -} - -export class MApps { - private readonly siweConfig?: Omit; - private readonly chainId: ChainId; - private readonly subnames: Subnames; - private readonly networks: NetworksWithProvider; - private readonly dev: boolean; - - constructor(params: MAppsParams) { - this.siweConfig = params.siweConfig; - this.chainId = params.chainId; - this.subnames = params.subnames; - this.networks = params.networks; - this.dev = params.dev; - } - - async checkIfMAppIsEnabled(params: { - mApp: string; - ens: string; - chainId?: ChainId; - }): Promise { - const chainId = params.chainId || this.chainId; - const network = this.networks.find((network) => network.chainId === chainId); - if (!network) { - throw new Error('Network not found'); - } - - - const mApp = params.mApp; - const ens = params.ens; - const records = await this.subnames.getRecords({ - ens, - chainId, - providerUrl: network.providerUrl - }); - - if (!records) { - return false; - } - - if (!records.isJAN) { - return false; - } - - const mAppField = records.records.texts.find((text) => text.key === 'mApps'); - - if (!mAppField) { - return false; - } - - const mAppFieldValue = JSON.parse(mAppField.value) as MApp; - - if (!mAppFieldValue) { - return false; - } - - return mAppFieldValue.mApps.includes(mApp); - } - - async canEnableMApps(params: { - ens: string; - chainId?: ChainId; - }): Promise { - const chainId = params.chainId || this.chainId; - const ens = params.ens; - const records = await this.subnames.getRecords({ - ens, - chainId - }); - - return records.isJAN; - } - - requestAddMAppPermissionChallenge(params: RequestAddMAppPermissionChallengeRoute['params']): Promise { - const { chainId, ttl, origin, domain,...rest } = params; - - const _chainId = chainId || this.chainId; - const _ttl = ttl || 120000; - const _origin = origin || this.siweConfig?.origin; - const _domain = domain || this.siweConfig?.domain; - return assertRestCall('SIWE_MAPP_ADD_PERMISSION_ROUTE', 'POST', { - ttl: _ttl, - chainId: _chainId, - origin: _origin, - domain: _domain, - ...rest - }, undefined, this.dev)(['ttl','chainId','origin','domain']) - } - - requestAppendMAppFieldChallenge(params: RequestAppendMAppFieldChallengeRoute['params']): Promise { - const { chainId, ttl, origin, domain,...rest } = params; - const _chainId = chainId || this.chainId; - const _ttl = ttl || 120000; - const _origin = origin || this.siweConfig?.origin; - const _domain = domain || this.siweConfig?.domain; - - return assertRestCall('SIWE_MAPP_APPEND_FIELD_ROUTE', 'POST', { - ttl: _ttl, - chainId: _chainId, - origin: _origin, - domain: _domain, - ...rest - }, undefined, this.dev)(['ttl','chainId','origin','domain']) - } - - requestRevokeMAppPermissionChallenge(params: RequestRevokeMAppPermissionChallengeRoute['params']): Promise { - const { chainId, ttl, origin, domain,...rest } = params; - const _chainId = chainId || this.chainId; - const _ttl = ttl || 120000; - const _origin = origin || this.siweConfig?.origin; - const _domain = domain || this.siweConfig?.domain; - - return assertRestCall('SIWE_MAPP_REVOKE_PERMISSION_ROUTE', 'POST', { - ttl: _ttl, - chainId: _chainId, - origin: _origin, - domain: _domain, - ...rest - }, undefined, this.dev)(['ttl','chainId','origin','domain']) - } - - addMAppPermission( - params: AddMAppPermissionRoute['params'], - ): Promise { - return assertRestCall('MAPP_ADD_PERMISSION_ROUTE', 'POST', { - ...params - }, undefined, this.dev)(['message','address','signature']) - } - - appendMAppField( - params: AppendMAppFieldRoute['params'], - headers: AppendMAppFieldRoute['headers'] - ): Promise { - return assertRestCall('MAPP_APPEND_FIELD_ROUTE', 'POST', { - ...params - }, { - ...headers - }, this.dev)(['subname','fields'],['xAddress','xMessage','xSignature']) - } - - revokeMAppPermission( - params: RevokeMAppPermissionRoute['params'] - ): Promise { - return assertRestCall('MAPP_REVOKE_PERMISSION_ROUTE', 'POST', { - ...params - }, undefined, this.dev)(['message','address','signature']) - } -} diff --git a/packages/@justaname.id/sdk/src/lib/features/sign-in/index.ts b/packages/@justaname.id/sdk/src/lib/features/sign-in/index.ts index 997da2f0..c7619201 100644 --- a/packages/@justaname.id/sdk/src/lib/features/sign-in/index.ts +++ b/packages/@justaname.id/sdk/src/lib/features/sign-in/index.ts @@ -10,6 +10,7 @@ import { OffchainResolvers } from '../offchain-resolvers'; import { RequestSignInParams, SignInFunctionParams } from '../../types/signin'; import { createPublicClient, http } from 'viem'; import { mainnet, sepolia } from 'viem/chains'; +import { normalize } from 'viem/ens'; export interface SignInResponse extends SiwensResponse { isJustaName: boolean; @@ -116,10 +117,16 @@ export class SignIn { domain: params.domain, }, { - provider: network.provider, + // Smart-contract (EIP-1271) verification is handled inside + // `verificationFallback` below using viem's `verifySiweMessage`. + // We no longer pass `provider` here because it must be an ethers + // `Provider`, and the SDK is now viem-only. verificationFallback: async (params, opts, message, EIP1271Promise) => { + // Use the chainId extracted from the SIWE message itself, not the + // SDK-default. Otherwise contract-wallet (EIP-1271) verification + // runs against the wrong chain when the message is cross-chain. const publicClient = createPublicClient({ - chain: this.chainId === 1 ? mainnet : sepolia, + chain: chainId === 1 ? mainnet : sepolia, transport: http(network.providerUrl), }); @@ -209,7 +216,13 @@ export class SignIn { } const [resolverAddress, resolvers] = await Promise.all([ - network.provider.getResolver(ens), + // Narrow the catch to "resolver not found" (the only expected miss). + // Network / RPC errors must bubble up so callers see a real failure + // rather than a misleading "ENS not registered" downstream. + network.provider.getEnsResolver({ name: normalize(ens) }).catch((e) => { + if (e?.name === 'EnsResolverNotFoundError') return undefined; + throw e; + }), this.offchainResolvers.getAllOffchainResolvers(), ]); @@ -221,11 +234,14 @@ export class SignIn { throw InvalidENSException.chainNotSupported(chainId.toString()); } - if (!resolverAddress?.address) { + if (!resolverAddress) { throw InvalidENSException.notRegisteredENS(ens); } - return currentOffchainResolver.resolverAddress === resolverAddress?.address; + return ( + currentOffchainResolver.resolverAddress.toLowerCase() === + resolverAddress.toLowerCase() + ); } } diff --git a/packages/@justaname.id/sdk/src/lib/features/subname-challenge/index.ts b/packages/@justaname.id/sdk/src/lib/features/subname-challenge/index.ts index f1aa62d0..5d065d22 100644 --- a/packages/@justaname.id/sdk/src/lib/features/subname-challenge/index.ts +++ b/packages/@justaname.id/sdk/src/lib/features/subname-challenge/index.ts @@ -115,8 +115,20 @@ export class SubnameChallenge { expirationTime, }); + const prepared = siweMessage.prepareMessage(); + + if (this.dev) { + // eslint-disable-next-line no-console + console.debug( + '[JustaName] SIWE challenge prepared (chainId:', + _chainId, + ')\n', + prepared + ); + } + return { - challenge: siweMessage.prepareMessage(), + challenge: prepared, }; } diff --git a/packages/@justaname.id/sdk/src/lib/justaname/index.ts b/packages/@justaname.id/sdk/src/lib/justaname/index.ts index ca6d2372..1a66ca52 100644 --- a/packages/@justaname.id/sdk/src/lib/justaname/index.ts +++ b/packages/@justaname.id/sdk/src/lib/justaname/index.ts @@ -7,7 +7,6 @@ import { NetworkWithProvider, } from '../types'; import { - MApps, OffchainResolvers, SignIn, SubnameChallenge, @@ -15,7 +14,13 @@ import { } from '../features'; import { InvalidConfigurationException } from '../errors/InvalidConfiguration.exception'; // import { providerUrlChainIdLoadingMap, providerUrlChainIdMap } from '../memory'; -import { getJsonRpcProvider } from '../utils/ethersCompat'; +import { createPublicClient, http, PublicClient } from 'viem'; +import { mainnet, sepolia } from 'viem/chains'; + +const buildPublicClient = (providerUrl: string, chainId: 1 | 11155111): PublicClient => { + const chain = chainId === 1 ? mainnet : sepolia; + return createPublicClient({ chain, transport: http(providerUrl) }); +}; /** * The main class for the JustaName SDK. @@ -68,26 +73,16 @@ export class JustaName { **/ signIn: SignIn; - /** - * The MApps feature. - * @public - * @type {MApps} - * @memberof JustaName - */ - mApps: MApps; - constructor( siwe: SubnameChallenge, subnames: Subnames, offchainResolvers: OffchainResolvers, - signIn: SignIn, - mApps: MApps + signIn: SignIn ) { this.siwe = siwe; this.subnames = subnames; this.offchainResolvers = offchainResolvers; this.signIn = signIn; - this.mApps = mApps; } static init(configuration: JustaNameConfig = {}): JustaName { @@ -146,20 +141,11 @@ export class JustaName { offchainResolvers, }); - const mApps = new MApps({ - siweConfig, - chainId: defaultChainId, - networks, - subnames, - dev, - }); - return new JustaName( subnameChallenge, subnames, offchainResolvers, - signIn, - mApps + signIn ); } @@ -167,11 +153,11 @@ export class JustaName { const defaultMainnetProviderUrl = 'https://cloudflare-eth.com'; const defaultTestnetProviderUrl = 'https://rpc.sepolia.org'; - const defaultMainnetProvider = getJsonRpcProvider( + const defaultMainnetProvider = buildPublicClient( defaultMainnetProviderUrl, 1 ); - const defaultTestnetProvider = getJsonRpcProvider( + const defaultTestnetProvider = buildPublicClient( defaultTestnetProviderUrl, 11155111 ); @@ -187,11 +173,6 @@ export class JustaName { provider: defaultTestnetProvider, providerUrl: defaultTestnetProviderUrl, }, - // { - // chainId: 31337 as ChainId, - // provider: getJsonRpcProvider('http://localhost:8545'), - // providerUrl: 'http://localhost:8545', - // }, ] as NetworksWithProvider; const baseNetworksConfig = baseNetworks.map((_network) => { @@ -199,7 +180,10 @@ export class JustaName { if (network && network?.providerUrl) { return { chainId: network.chainId, - provider: getJsonRpcProvider(network.providerUrl), + provider: buildPublicClient( + network.providerUrl, + network.chainId as 1 | 11155111 + ), providerUrl: network.providerUrl, }; } else { @@ -213,9 +197,6 @@ export class JustaName { const testnetNetwork = baseNetworksConfig.find( (n) => n.chainId === 11155111 ) as NetworkWithProvider<11155111>; - // const localNetwork = baseNetworksConfig.find( - // (n) => n.chainId === 31337 - // ) as NetworkWithProvider<31337>; if (!mainnetNetwork) { throw new InvalidConfigurationException('The mainnet network is missing'); } @@ -231,51 +212,4 @@ export class JustaName { // To be optimized for serverless and re added later // this.checkNetworks(configuration.networks); } - - // private static checkNetworks(networks: Networks): void { - // if (networks && networks.length > 0) { - // networks.reduce((acc, network) => { - // if (acc.includes(network.chainId)) { - // throw new InvalidConfigurationException('The chainId is duplicated'); - // } - // return [...acc, network.chainId]; - // }, [] as ChainId[]); - // - // networks.forEach((network) => { - // if (providerUrlChainIdLoadingMap.has(network.providerUrl)) { - // if (providerUrlChainIdLoadingMap.get(network.providerUrl)) { - // return; - // } - // } - // - // providerUrlChainIdLoadingMap.set(network.providerUrl, true); - // - // if (providerUrlChainIdMap.has(network.providerUrl)) { - // if ( - // providerUrlChainIdMap.get(network.providerUrl) !== network.chainId - // ) { - // throw new InvalidConfigurationException( - // 'The chainId does not match the chainId of the providerUrl' - // ); - // } else { - // return; - // } - // } - // - // const provider = getJsonRpcProvider(network.providerUrl); - // provider.getNetwork().then((_network) => { - // if (network.chainId.toString() !== _network.chainId.toString()) { - // throw new InvalidConfigurationException( - // 'The chainId does not match the chainId of the providerUrl' - // ); - // } - // - // providerUrlChainIdMap.set( - // network.providerUrl, - // parseInt(_network.chainId.toString()) - // ); - // }); - // }); - // } - // } } diff --git a/packages/@justaname.id/sdk/src/lib/types/index.ts b/packages/@justaname.id/sdk/src/lib/types/index.ts index 4deaba43..6de4f444 100644 --- a/packages/@justaname.id/sdk/src/lib/types/index.ts +++ b/packages/@justaname.id/sdk/src/lib/types/index.ts @@ -7,5 +7,4 @@ export * from './offchain-resolver'; export * from './primary-name'; export * from './siwe'; export * from './subnames'; -export * from './mApps'; export * from './signin'; diff --git a/packages/@justaname.id/sdk/src/lib/types/justaname/configuration.ts b/packages/@justaname.id/sdk/src/lib/types/justaname/configuration.ts index 85b12ad9..745168c9 100644 --- a/packages/@justaname.id/sdk/src/lib/types/justaname/configuration.ts +++ b/packages/@justaname.id/sdk/src/lib/types/justaname/configuration.ts @@ -1,9 +1,9 @@ -import { JsonRpcProvider } from '../../utils/ethersCompat'; +import type { PublicClient } from 'viem'; import { ChainId } from '../common'; export interface NetworkWithProvider extends Network { - provider: JsonRpcProvider; + provider: PublicClient; } export interface Network { diff --git a/packages/@justaname.id/sdk/src/lib/types/mApps/add-mApp-permission.ts b/packages/@justaname.id/sdk/src/lib/types/mApps/add-mApp-permission.ts deleted file mode 100644 index 8073dc05..00000000 --- a/packages/@justaname.id/sdk/src/lib/types/mApps/add-mApp-permission.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { IRequest, IRoute, SubnameResponse } from '../common'; - -export interface AddMAppPermissionRequest extends IRequest { - address: string; - signature?: string; - message: string; -} - -export interface AddMAppPermissionRoute - extends IRoute {} diff --git a/packages/@justaname.id/sdk/src/lib/types/mApps/append-mApp-field.ts b/packages/@justaname.id/sdk/src/lib/types/mApps/append-mApp-field.ts deleted file mode 100644 index 2a9bfb9f..00000000 --- a/packages/@justaname.id/sdk/src/lib/types/mApps/append-mApp-field.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IRequest, IRoute, SubnameResponse } from '../common'; -import { SIWEHeaders } from '../headers'; - -export interface AppendMAppFieldsRequest { - key: string; - value: string; -} - -export interface AppendMAppFieldRequest extends IRequest{ - subname: string; - fields: AppendMAppFieldsRequest[]; -} - -export interface AppendMAppFieldRoute extends IRoute {} \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/types/mApps/index.ts b/packages/@justaname.id/sdk/src/lib/types/mApps/index.ts deleted file mode 100644 index c90cffbf..00000000 --- a/packages/@justaname.id/sdk/src/lib/types/mApps/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './add-mApp-permission' -export * from './append-mApp-field' -export * from './revoke-mApp-permission' -export * from './mApp' \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/types/mApps/mApp.ts b/packages/@justaname.id/sdk/src/lib/types/mApps/mApp.ts deleted file mode 100644 index e2ee2fbe..00000000 --- a/packages/@justaname.id/sdk/src/lib/types/mApps/mApp.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface MApp { - mApps: string[] -} \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/types/mApps/revoke-mApp-permission.ts b/packages/@justaname.id/sdk/src/lib/types/mApps/revoke-mApp-permission.ts deleted file mode 100644 index c1145ab5..00000000 --- a/packages/@justaname.id/sdk/src/lib/types/mApps/revoke-mApp-permission.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { IRequest, IRoute, SubnameResponse } from '../common'; - -export interface RevokeMAppPermissionRequest extends IRequest { - address: string; - signature?: string; - message: string; -} - -export interface RevokeMAppPermissionRoute - extends IRoute {} diff --git a/packages/@justaname.id/sdk/src/lib/types/siwe/add-mApp-permission-challenge.ts b/packages/@justaname.id/sdk/src/lib/types/siwe/add-mApp-permission-challenge.ts deleted file mode 100644 index bd337f5b..00000000 --- a/packages/@justaname.id/sdk/src/lib/types/siwe/add-mApp-permission-challenge.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { ChainId, EmptyHeaders, IRequest, IResponse, IRoute } from '../common'; - -/** - * Represents a request to challenge to add mApp permission. - * @interface RequestAddMAppPermissionChallengeRequest - * @public - */ - -export interface RequestAddMAppPermissionChallengeRequest extends IRequest { - - /** - * Represents the ENS domain - * @type {string} - */ - domain: string; - - /** - * Represents the ethereum address to be challenged. - * @type {string} - */ - address: string; - - /** - * Represents the origin of the request (e.g. the domain of the website). - * @type {string} - */ - origin: string; - - /** - * Represents the chainId of the blockchain to be used. - * @type {1 | 11155111} - */ - chainId: ChainId; - - /** - * Specifies the time-to-live (TTL) for a variable. - * default: 120000 ms, 2 minutes ( 2 * 60 * 1000 ) - * @type {number} - * @default 120000 - * @optional - */ - ttl?: number; - - /** - * Subname requesting the ABDC Permission - * @type {string} - */ - subname: string - - /** - * Subname requesting the MApps Permission - * @type {string} - */ - mApp: string -} - -/** - * Represents the response to a request to challenge a specific address using SIWE. - * @interface RequestAddMAppPermissionChallengeResponse - * @public - */ -export interface RequestAddMAppPermissionChallengeResponse extends IResponse{ - /** - * Represents the challenge to be signed by the user. - * @type {string} - */ - challenge: string; -} - -export interface RequestAddMAppPermissionChallengeRoute extends IRoute {} \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/types/siwe/append-mApp-field-challenge.ts b/packages/@justaname.id/sdk/src/lib/types/siwe/append-mApp-field-challenge.ts deleted file mode 100644 index 17442633..00000000 --- a/packages/@justaname.id/sdk/src/lib/types/siwe/append-mApp-field-challenge.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { ChainId, EmptyHeaders, IRequest, IResponse, IRoute } from '../common'; - -/** - * Represents a request to challenge to add mApp permission. - * @interface RequestAppendMAppFieldChallengeRequest - * @public - */ - -export interface RequestAppendMAppFieldChallengeRequest extends IRequest { - - /** - * Represents the ENS domain - * @type {string} - */ - domain: string; - - /** - * Represents the ethereum address to be challenged. - * @type {string} - */ - address: string; - - /** - * Represents the origin of the request (e.g. the domain of the website). - * @type {string} - */ - origin: string; - - /** - * Represents the chainId of the blockchain to be used. - * @type {1 | 11155111} - */ - chainId: ChainId; - - /** - * Specifies the time-to-live (TTL) for a variable. - * default: 120000 ms, 2 minutes ( 2 * 60 * 1000 ) - * @type {number} - * @default 120000 - * @optional - */ - ttl?: number; - - /** - * Subname requesting the ABDC Permission - * @type {string} - */ - subname: string - - /** - * Subname requesting the MApps Permission - * @type {string} - */ - mApp: string -} - -/** - * Represents the response to a request to challenge a specific address using SIWE. - * @interface RequestAppendMAppFieldChallengeResponse - * @public - */ -export interface RequestAppendMAppFieldChallengeResponse extends IResponse{ - /** - * Represents the challenge to be signed by the user. - * @type {string} - */ - challenge: string; -} - -export interface RequestAppendMAppFieldChallengeRoute extends IRoute {} \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/types/siwe/index.ts b/packages/@justaname.id/sdk/src/lib/types/siwe/index.ts index 065120fe..ca7981d3 100644 --- a/packages/@justaname.id/sdk/src/lib/types/siwe/index.ts +++ b/packages/@justaname.id/sdk/src/lib/types/siwe/index.ts @@ -1,6 +1,3 @@ export * from './request-challenge' export * from './verify-challenge' -export * from './add-mApp-permission-challenge' -export * from './append-mApp-field-challenge' -export * from './revoke-mApp-permission-challenge' export * from './siwe-config' \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/types/siwe/revoke-mApp-permission-challenge.ts b/packages/@justaname.id/sdk/src/lib/types/siwe/revoke-mApp-permission-challenge.ts deleted file mode 100644 index 8de57423..00000000 --- a/packages/@justaname.id/sdk/src/lib/types/siwe/revoke-mApp-permission-challenge.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ChainId, EmptyHeaders, IRequest, IResponse, IRoute } from '../common'; - -export interface RequestRevokeMAppPermissionChallengeRequest extends IRequest { - - /** - * Represents the ENS domain - * @type {string} - */ - domain: string; - - /** - * Represents the ethereum address to be challenged. - * @type {string} - */ - address: string; - - /** - * Represents the origin of the request (e.g. the domain of the website). - * @type {string} - */ - origin: string; - - /** - * Represents the chainId of the blockchain to be used. - * @type {1 | 11155111} - */ - chainId: ChainId; - - /** - * Specifies the time-to-live (TTL) for a variable. - * default: 120000 ms, 2 minutes ( 2 * 60 * 1000 ) - * @type {number} - * @default 120000 - * @optional - */ - ttl?: number; - - /** - * Subname requesting the ABDC Permission - * @type {string} - */ - subname: string - - /** - * Subname requesting the MApps Permission - * @type {string} - */ - mApp: string -} - -/** - * Represents the response to a request to challenge a specific address using SIWE. - * @interface RequestRevokeMAppPermissionChallengeResponse - * @public - */ -export interface RequestRevokeMAppPermissionChallengeResponse extends IResponse{ - /** - * Represents the challenge to be signed by the user. - * @type {string} - */ - challenge: string; -} - -export interface RequestRevokeMAppPermissionChallengeRoute extends IRoute {} \ No newline at end of file diff --git a/packages/@justaname.id/sdk/src/lib/utils/ethersCompat.ts b/packages/@justaname.id/sdk/src/lib/utils/ethersCompat.ts deleted file mode 100644 index 682e9f25..00000000 --- a/packages/@justaname.id/sdk/src/lib/utils/ethersCompat.ts +++ /dev/null @@ -1,51 +0,0 @@ -// inspired by spruceid siwe: https://github.com/spruceid/siwe/blob/main/packages/siwe/lib/ethersCompat.ts - -import { ethers } from 'ethers'; - -// @ts-expect-error -- compatibility hack -type ProviderV5 = ethers.providers.Provider; -type ProviderV6 = ethers.Provider; -// @ts-expect-error -- compatibility hack -type JsonRpcProviderV5 = ethers.providers.JsonRpcProvider; -type JsonRpcProviderV6 = ethers.JsonRpcProvider; - -export type Provider = ProviderV6 extends undefined ? ProviderV5 : ProviderV6; -export type JsonRpcProvider = JsonRpcProviderV6 extends undefined - ? JsonRpcProviderV5 - : JsonRpcProviderV6; - -interface EthersCompat { - namehash?: (name: string) => string; - getAddress?: (address: string) => string; - JsonRpcProvider?: new (...args: any[]) => JsonRpcProvider; - utils: { - namehash: (name: string) => string; - getAddress: (address: string) => string; - }; - providers: { - JsonRpcProvider: new (...args: any[]) => JsonRpcProvider; - }; -} - -const ethersCompat = ethers as unknown as EthersCompat; - -export const getJsonRpcProvider = ( - providerUrl?: string, - chainId?: number -): JsonRpcProvider => { - if ('JsonRpcProvider' in ethersCompat) { - return new ethersCompat.JsonRpcProvider!(providerUrl, chainId); - } else { - return new ethersCompat.providers.JsonRpcProvider(providerUrl, chainId); - } -}; - -export const namehash: (name: string) => string = - 'namehash' in ethersCompat - ? ethersCompat.namehash! - : ethersCompat.utils.namehash; - -export const getAddress: (address: string) => string = - 'getAddress' in ethersCompat - ? ethersCompat.getAddress! - : ethersCompat.utils.getAddress; diff --git a/packages/@justaname.id/sdk/src/test/features/sign-in/sign-in.spec.ts b/packages/@justaname.id/sdk/src/test/features/sign-in/sign-in.spec.ts index 7ad6d5a5..2b55366c 100644 --- a/packages/@justaname.id/sdk/src/test/features/sign-in/sign-in.spec.ts +++ b/packages/@justaname.id/sdk/src/test/features/sign-in/sign-in.spec.ts @@ -1,4 +1,8 @@ -import { ethers } from 'ethers'; +import { + generatePrivateKey, + privateKeyToAccount, + type PrivateKeyAccount, +} from 'viem/accounts'; import * as dotenv from 'dotenv'; import SignIn from '../../../lib/features/sign-in'; import { OffchainResolvers } from '../../../lib/features'; @@ -14,18 +18,34 @@ const URI = 'https://' + DOMAIN; const CHAIN_ID = (parseInt(process.env["SDK_CHAIN_ID"] as string) || 11155111) as ChainId const VALID_TTL = 60 * 60 * 24 * 1000; // 1 day -const invalidSigner = new ethers.Wallet( - ethers.Wallet.createRandom().privateKey -); +interface TestSigner { + address: string; + signMessage(message: string): Promise; +} +const toTestSigner = (account: PrivateKeyAccount): TestSigner => ({ + address: account.address, + signMessage: (message: string) => account.signMessage({ message }), +}); +const randomTestSigner = (): TestSigner => + toTestSigner(privateKeyToAccount(generatePrivateKey())); + +const invalidSigner = randomTestSigner(); const ENS_DOMAIN = process.env['SDK_ENS_DOMAIN'] as string; -const subnameSigner = ethers.Wallet.createRandom(); +const subnameSigner = randomTestSigner(); const subnameToBeAdded = Math.random().toString(36).substring(7); const validApiKey = process.env['SDK_JUSTANAME_TEST_API_KEY'] as string; const JUSTANAME_ENV = process.env['SDK_JUSTANAME_DEV'] === 'true'; const SEPOLIA_PROVIDER_URL = process.env['SDK_SEPOLIA_PROVIDER_URL'] as string; const MAINNET_PROVIDER_URL = process.env['SDK_MAINNET_PROVIDER_URL'] as string; -describe('SignIn', () => { +// Integration tests require a live API key + provider URLs. Skip cleanly +// when env is not configured so unit-level assertions can still run. +const INTEGRATION_ENABLED = Boolean( + validApiKey && SEPOLIA_PROVIDER_URL && MAINNET_PROVIDER_URL +); +const describeIntegration = INTEGRATION_ENABLED ? describe : describe.skip; + +describeIntegration('SignIn', () => { let signIn: SignIn; let justaname: JustaName; @@ -112,7 +132,7 @@ describe('SignIn', () => { address: invalidSigner.address, ens: subnameToBeAdded + '.' + ENS_DOMAIN, }); - const signer2 = new ethers.Wallet(ethers.Wallet.createRandom().privateKey); + const signer2 = randomTestSigner(); const signature = await signer2.signMessage(message); try { await signIn.signIn({ message, signature }); diff --git a/packages/@justaname.id/sdk/src/test/features/subname-challenge/subname-challenge.spec.ts b/packages/@justaname.id/sdk/src/test/features/subname-challenge/subname-challenge.spec.ts index 9e48fbaa..9a04b389 100644 --- a/packages/@justaname.id/sdk/src/test/features/subname-challenge/subname-challenge.spec.ts +++ b/packages/@justaname.id/sdk/src/test/features/subname-challenge/subname-challenge.spec.ts @@ -1,10 +1,14 @@ import { SubnameChallenge } from '../../../lib/features/subname-challenge'; // import rest from '../../../lib/api/rest'; import dotenv from 'dotenv'; -import { ethers } from 'ethers'; +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; dotenv.config(); const JUSTANAME_ENV = process.env['SDK_JUSTANAME_DEV'] === 'true' -const signer = ethers.Wallet.createRandom(); +const account = privateKeyToAccount(generatePrivateKey()); +const signer = { + address: account.address, + signMessage: (message: string) => account.signMessage({ message }), +}; describe('subnameChallenge', () => { let subnameChallenge: SubnameChallenge; diff --git a/packages/@justaname.id/sdk/src/test/integration/justaname.spec.ts b/packages/@justaname.id/sdk/src/test/integration/justaname.spec.ts index 6dccf031..eec2f855 100644 --- a/packages/@justaname.id/sdk/src/test/integration/justaname.spec.ts +++ b/packages/@justaname.id/sdk/src/test/integration/justaname.spec.ts @@ -1,26 +1,44 @@ import { JustaName } from '../../lib/justaname'; import { configureEnv } from '../helpers/configureEnv'; import { initializeJustaName } from '../helpers/initializeJustaName'; -import { ethers } from 'ethers'; +import { + generatePrivateKey, + privateKeyToAccount, + type PrivateKeyAccount, +} from 'viem/accounts'; import * as dotenv from 'dotenv'; import { ChainId } from '../../lib/types'; import { ChallengeRequestException } from '../../lib/errors/ChallengeRequest.expection'; dotenv.config(); +interface TestSigner { + address: string; + signMessage(message: string): Promise; +} + +const toTestSigner = (account: PrivateKeyAccount): TestSigner => ({ + address: account.address, + signMessage: (message: string) => account.signMessage({ message }), +}); + const validApiKey = process.env['SDK_JUSTANAME_TEST_API_KEY'] as string; jest.setTimeout(50000); -const mAppPk = process.env['SDK_MAPP_PRIVATE_KEY'] as string; -const mAppSigner = new ethers.Wallet(mAppPk); -const subnameSigner = ethers.Wallet.createRandom(); +const subnameSigner = toTestSigner(privateKeyToAccount(generatePrivateKey())); const subnameToBeAdded = Math.random().toString(36).substring(6); const CHAIN_ID = parseInt(process.env['SDK_CHAIN_ID'] as string) as ChainId; const ENS_DOMAIN = process.env['SDK_ENS_DOMAIN'] as string; -const MAPP = process.env['SDK_MAPP'] as string; -const MAPP_2 = MAPP.split('.')[0] + '2' + '.' + MAPP.split('.')[1]; const SEPOLIA_PROVIDER_URL = process.env['SDK_SEPOLIA_PROVIDER_URL'] as string; const MAINNET_PROVIDER_URL = process.env['SDK_MAINNET_PROVIDER_URL'] as string; -describe('justaname', () => { + +// Integration tests require live env vars (API key, provider URLs, ENS +// domain). Skip the entire suite cleanly when not configured. +const INTEGRATION_ENABLED = Boolean( + validApiKey && ENS_DOMAIN && SEPOLIA_PROVIDER_URL && MAINNET_PROVIDER_URL +); +const describeIntegration = INTEGRATION_ENABLED ? describe : describe.skip; + +describeIntegration('justaname', () => { let justaname: JustaName; beforeAll(async () => { @@ -353,37 +371,6 @@ describe('justaname', () => { }); }); - it("mApps shouldn't be updated", async () => { - const challenge = await justaname.siwe.requestChallenge({ - address: subnameSigner.address, - chainId: CHAIN_ID, - }); - - const signature = await subnameSigner.signMessage(challenge.challenge); - - const response = await justaname.subnames.updateSubname( - { - username: subnameToBeAdded, - chainId: CHAIN_ID, - ensDomain: ENS_DOMAIN, - text: { - mApps: 'shouldntBeUpdated', - [`test_${MAPP}`]: 'shouldBeOverrideWhenMAppPermissionIsAdded', - }, - signature, - }, - { - xMessage: challenge.challenge, - xAddress: subnameSigner.address, - } - ); - - const mApps = response.records.texts.find( - (text) => text.key === 'mApps' - )?.value; - expect(mApps).toBeUndefined(); - }); - it('should remove test if value is empty', async () => { const challenge = await justaname.siwe.requestChallenge({ address: subnameSigner.address, @@ -430,95 +417,6 @@ describe('justaname', () => { expect(response).toBeDefined(); }); - it("should return false if ens can't enable mApps", async () => { - const canEnable = await justaname.mApps.canEnableMApps({ - ens: 'justatest2.eth', - chainId: CHAIN_ID, - }); - - expect(canEnable).toBeFalsy(); - }); - - it('should return true if ens can enable mApps', async () => { - const canEnable = await justaname.mApps.canEnableMApps({ - ens: subnameToBeAdded + '.' + ENS_DOMAIN, - chainId: CHAIN_ID, - }); - - expect(canEnable).toBeTruthy(); - }); - - it("shouldn't have mApps enabled", async () => { - const subname = await justaname.mApps.checkIfMAppIsEnabled({ - ens: subnameToBeAdded + '.' + ENS_DOMAIN, - mApp: MAPP, - chainId: CHAIN_ID, - }); - - expect(subname).toBeFalsy(); - }); - - it('should add mApps permission', async () => { - const challenge = await justaname.mApps.requestAddMAppPermissionChallenge({ - address: subnameSigner.address, - subname: subnameToBeAdded + '.' + ENS_DOMAIN, - mApp: MAPP, - chainId: CHAIN_ID, - }); - - const signature = await subnameSigner.signMessage(challenge.challenge); - const response = await justaname.mApps.addMAppPermission({ - address: subnameSigner.address, - signature, - message: challenge.challenge, - }); - - expect(response).toBeDefined(); - }); - - it('should add mApps2 permission', async () => { - const challenge = await justaname.mApps.requestAddMAppPermissionChallenge({ - address: subnameSigner.address, - subname: subnameToBeAdded + '.' + ENS_DOMAIN, - mApp: MAPP_2, - chainId: CHAIN_ID, - }); - - const signature = await subnameSigner.signMessage(challenge.challenge); - const response = await justaname.mApps.addMAppPermission({ - address: subnameSigner.address, - signature, - message: challenge.challenge, - }); - - expect(response).toBeDefined(); - }); - - it('should have removed test_mApps', async () => { - const subname = await justaname.subnames.getRecords({ - ens: subnameToBeAdded + '.' + ENS_DOMAIN, - chainId: CHAIN_ID, - }); - - const testMApps = subname.records.texts.find( - (text) => text.key === `test_${MAPP}` - )?.value; - - expect(testMApps).toBeUndefined(); - }); - - it('should have mApps enabled', async () => { - const mapp = await justaname.mApps.checkIfMAppIsEnabled({ - ens: subnameToBeAdded + '.' + ENS_DOMAIN, - mApp: MAPP, - chainId: CHAIN_ID, - }); - - expect(mapp).toBeTruthy(); - }); - - - it('should be remove contentHash', async () => { const records = await justaname.subnames.getRecords({ ens: subnameToBeAdded + '.' + ENS_DOMAIN, @@ -561,33 +459,6 @@ describe('justaname', () => { expect(response.records.contentHash).toBeNull(); }); - it('should revoke mApps permission', async () => { - const challenge = - await justaname.mApps.requestRevokeMAppPermissionChallenge({ - subname: subnameToBeAdded + '.' + ENS_DOMAIN, - address: subnameSigner.address, - mApp: MAPP, - chainId: CHAIN_ID, - }); - - const signature = await subnameSigner.signMessage(challenge.challenge); - - const response = await justaname.mApps.revokeMAppPermission({ - address: subnameSigner.address, - signature, - message: challenge.challenge, - }); - - const mApps = response.records.texts.find( - (text) => text.key === 'mApps' - )?.value; - const testJawEth = response.records.texts.find( - (text) => text.key === `test_${MAPP}` - )?.value; - - expect(mApps).toEqual(`{"mApps":["${MAPP_2}"]}`); - expect(testJawEth).toEqual(undefined); - }); it('should get all subnames', async () => { const subnames = await justaname.subnames.getSubnamesByAddress({ diff --git a/packages/@justaname.id/siwens/package.json b/packages/@justaname.id/siwens/package.json index 362acfc1..1125c483 100644 --- a/packages/@justaname.id/siwens/package.json +++ b/packages/@justaname.id/siwens/package.json @@ -1,9 +1,12 @@ { "name": "@justaname.id/siwens", "version": "0.0.145", + "dependencies": { + "punycode": "^2.3.1" + }, "peerDependencies": { - "ethers": "^5.6.8 || ^6.0.8", - "siwe": ">=2.0.0" + "siwe": ">=2.0.0", + "viem": "^2.48.0" }, "exports": { "./package.json": "./dist/package.json", diff --git a/packages/@justaname.id/siwens/src/lib/siwens/siwens.ts b/packages/@justaname.id/siwens/src/lib/siwens/siwens.ts index c85ddcb7..735df385 100644 --- a/packages/@justaname.id/siwens/src/lib/siwens/siwens.ts +++ b/packages/@justaname.id/siwens/src/lib/siwens/siwens.ts @@ -18,7 +18,30 @@ import { extractDataFromStatement, } from '../utils'; import { toASCII, toUnicode } from 'punycode'; -import { getJsonRpcProvider, JsonRpcProvider } from '../utils/ethersCompat'; +import { + createPublicClient, + http, + PublicClient, + isAddressEqual, + getAddress as viemGetAddress, +} from 'viem'; +import { mainnet, sepolia } from 'viem/chains'; +import type { Chain } from 'viem'; +import { normalize } from 'viem/ens'; + +const SUPPORTED_CHAINS: Record = { + 1: mainnet, + 11155111: sepolia, +}; + +const buildPublicClient = ( + providerUrl?: string, + chainId?: number +): PublicClient => + createPublicClient({ + chain: SUPPORTED_CHAINS[chainId ?? 1] ?? mainnet, + transport: http(providerUrl), + }); export interface SiwensResponse extends SiweResponse { ens: string; @@ -40,7 +63,7 @@ export interface SiwensConfig { } export class SIWENS extends SiweMessage { - readonly provider: JsonRpcProvider; + readonly provider: PublicClient; readonly providerUrl: string | undefined; constructor(signInConfig: SiwensConfig) { @@ -50,7 +73,7 @@ export class SIWENS extends SiweMessage { if (!providerUrl) { throw InvalidConfigurationException.providerUrlRequired(); } - this.provider = getJsonRpcProvider(providerUrl); + this.provider = buildPublicClient(providerUrl, this.chainId); this.providerUrl = providerUrl; return; } @@ -91,7 +114,7 @@ export class SIWENS extends SiweMessage { expirationTime, }); this.providerUrl = providerUrl; - this.provider = getJsonRpcProvider(providerUrl); + this.provider = buildPublicClient(providerUrl, this.chainId); } override async verify( @@ -151,12 +174,14 @@ export class SIWENS extends SiweMessage { } private async verifyEnsAddress(ens: string, address: string) { - const resolvedAddress = await this.provider.resolveName(ens); + const resolvedAddress = await this.provider.getEnsAddress({ + name: normalize(ens), + }); if (!resolvedAddress) { throw InvalidENSException.notRegisteredENS(ens); } - if (resolvedAddress !== address) { + if (!isAddressEqual(resolvedAddress, viemGetAddress(address))) { throw InvalidENSException.invalidENSOwner(ens, address); } return true; diff --git a/packages/@justaname.id/siwens/src/lib/utils/ethersCompat.ts b/packages/@justaname.id/siwens/src/lib/utils/ethersCompat.ts deleted file mode 100644 index 682e9f25..00000000 --- a/packages/@justaname.id/siwens/src/lib/utils/ethersCompat.ts +++ /dev/null @@ -1,51 +0,0 @@ -// inspired by spruceid siwe: https://github.com/spruceid/siwe/blob/main/packages/siwe/lib/ethersCompat.ts - -import { ethers } from 'ethers'; - -// @ts-expect-error -- compatibility hack -type ProviderV5 = ethers.providers.Provider; -type ProviderV6 = ethers.Provider; -// @ts-expect-error -- compatibility hack -type JsonRpcProviderV5 = ethers.providers.JsonRpcProvider; -type JsonRpcProviderV6 = ethers.JsonRpcProvider; - -export type Provider = ProviderV6 extends undefined ? ProviderV5 : ProviderV6; -export type JsonRpcProvider = JsonRpcProviderV6 extends undefined - ? JsonRpcProviderV5 - : JsonRpcProviderV6; - -interface EthersCompat { - namehash?: (name: string) => string; - getAddress?: (address: string) => string; - JsonRpcProvider?: new (...args: any[]) => JsonRpcProvider; - utils: { - namehash: (name: string) => string; - getAddress: (address: string) => string; - }; - providers: { - JsonRpcProvider: new (...args: any[]) => JsonRpcProvider; - }; -} - -const ethersCompat = ethers as unknown as EthersCompat; - -export const getJsonRpcProvider = ( - providerUrl?: string, - chainId?: number -): JsonRpcProvider => { - if ('JsonRpcProvider' in ethersCompat) { - return new ethersCompat.JsonRpcProvider!(providerUrl, chainId); - } else { - return new ethersCompat.providers.JsonRpcProvider(providerUrl, chainId); - } -}; - -export const namehash: (name: string) => string = - 'namehash' in ethersCompat - ? ethersCompat.namehash! - : ethersCompat.utils.namehash; - -export const getAddress: (address: string) => string = - 'getAddress' in ethersCompat - ? ethersCompat.getAddress! - : ethersCompat.utils.getAddress; diff --git a/packages/@justaname.id/siwens/src/test/siwens.spec.ts b/packages/@justaname.id/siwens/src/test/siwens.spec.ts index 8b1335ca..5a5c9ae7 100644 --- a/packages/@justaname.id/siwens/src/test/siwens.spec.ts +++ b/packages/@justaname.id/siwens/src/test/siwens.spec.ts @@ -1,4 +1,8 @@ -import { ethers } from 'ethers'; +import { + generatePrivateKey, + privateKeyToAccount, + type PrivateKeyAccount, +} from 'viem/accounts'; import * as dotenv from 'dotenv'; import { SIWENS, @@ -7,8 +11,31 @@ import { } from '../'; dotenv.config(); -const pk = process.env['SIWENS_PRIVATE_KEY'] as string; -const signer = new ethers.Wallet(pk); +interface TestSigner { + address: string; + signMessage(message: string): Promise; +} +const toTestSigner = (account: PrivateKeyAccount): TestSigner => ({ + address: account.address, + signMessage: (message: string) => account.signMessage({ message }), +}); +const randomTestSigner = (): TestSigner => + toTestSigner(privateKeyToAccount(generatePrivateKey())); + +// Integration tests are gated on env vars — they hit a real Sepolia provider +// and require a funded test wallet. When the env is not configured we still +// want unit-level tests (TTL validation, ENS format, nonce) to run, so we +// stub `signer` with a deterministic random account and use `itIntegration` +// for tests that actually need the configured wallet/provider. +const rawPk = process.env['SIWENS_PRIVATE_KEY']; +// CI sets unconfigured env vars to the empty string (not undefined), so we +// need to treat empty as "not provided" before handing it to viem. +const pk = rawPk && rawPk.startsWith('0x') ? (rawPk as `0x${string}`) : undefined; +const INTEGRATION_ENABLED = Boolean(pk && process.env['SIWENS_PROVIDER_URL']); +const itIntegration = INTEGRATION_ENABLED ? it : it.skip; +const signer = toTestSigner( + privateKeyToAccount(pk ?? generatePrivateKey()) +); const PROVIDER_URL = process.env['SIWENS_PROVIDER_URL'] as string; const DOMAIN = 'justaname.id'; const URI = 'https://' + DOMAIN; @@ -20,7 +47,9 @@ const VALID_TTL = 60 * 60 * 24 * 1000; // 1 day const TTL_LESS_THAN_ZERO = -1; const TTL_GREATER_THAN_MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER + 1; const INVALID_ENS = 'justaname'; -const VALID_ENS = process.env['SIWENS_VALID_ENS'] as string; +// Fallback ENS for unit-level tests (the value is only consequential for +// the integration tests guarded by `itIntegration`). +const VALID_ENS = (process.env['SIWENS_VALID_ENS'] as string) || 'test.eth'; describe('SIWENS', () => { @@ -132,7 +161,7 @@ describe('SIWENS', () => { expect(signature).toBeTruthy(); }); - it('should verify a valid signature', async () => { + itIntegration('should verify a valid signature', async () => { const signature = await signer.signMessage(message); const address = await new SIWENS({ @@ -144,7 +173,7 @@ describe('SIWENS', () => { expect(address.success).toBeTruthy(); },60000) - it('should return ens in the response', async () => { + itIntegration('should return ens in the response', async () => { const signature = await signer.signMessage(message); const address = await new SIWENS({ params:message, @@ -155,8 +184,8 @@ describe('SIWENS', () => { expect(address.ens).toBe(VALID_ENS); },60000) - it('should return ens in the failed response', async () => { - const signer2 = ethers.Wallet.createRandom(); + itIntegration('should return ens in the failed response', async () => { + const signer2 = randomTestSigner(); const signature = await signer2.signMessage(message); try { await new SIWENS({ @@ -172,8 +201,8 @@ describe('SIWENS', () => { throw new Error('Should have thrown an error'); },60000) - it('should throw an error if address isn\'t owner of ens', async () => { - const signer = ethers.Wallet.createRandom(); + itIntegration('should throw an error if address isn\'t owner of ens', async () => { + const signer = randomTestSigner(); const siwens = new SIWENS({ params: { domain: DOMAIN, diff --git a/packages/@justverified/plugin/src/lib/components/EmailCredentialItem/index.tsx b/packages/@justverified/plugin/src/lib/components/EmailCredentialItem/index.tsx index e9273288..9f4b9bfa 100644 --- a/packages/@justverified/plugin/src/lib/components/EmailCredentialItem/index.tsx +++ b/packages/@justverified/plugin/src/lib/components/EmailCredentialItem/index.tsx @@ -11,7 +11,6 @@ export interface EmailCredentialItemProps { credentialValue: EthereumEip712Signature2021<{ email: string }> | undefined; disabled?: boolean; refetchRecords: () => void; - mAppsAlreadyEnabled: string[] | undefined; mApp: string; } @@ -22,7 +21,6 @@ export const EmailCredentialItem: FC = ({ verificationBackendUrl, disabled = false, refetchRecords, - mAppsAlreadyEnabled, mApp, }) => { const [email, setEmail] = useState(''); @@ -65,7 +63,6 @@ export const EmailCredentialItem: FC = ({ verificationBackendUrl={verificationBackendUrl} refetchVerifyRecords={refetchVerifyRecords} refetchRecords={refetchRecords} - mAppsAlreadyEnabled={mAppsAlreadyEnabled} mApp={mApp} /> void; refetchRecords: () => void; - mAppsAlreadyEnabled: string[] | undefined; mApp: string; open: boolean; email: string | undefined; @@ -38,7 +37,6 @@ export interface EmailDialogProps { export const EmailDialog: FC = ({ refetchRecords, mApp, - mAppsAlreadyEnabled, refetchVerifyRecords, open, email, @@ -250,35 +248,21 @@ export const EmailDialog: FC = ({ const key = 'email'; const vc = res.verifiableCredential; const value = vc.credentialSubject.email; - if (mAppsAlreadyEnabled?.includes(mApp)) { - updateRecords({ - text: [ - { - key: key, - value: value, - }, - ], - }).then(() => { - refetchRecords(); - refetchVerifyRecords(); - }); - } else { - updateRecords({ - text: [ - { - key: key, - value: value, - }, - { - key: res.dataKey, - value: JSON.stringify(vc), - }, - ], - }).then(() => { - refetchRecords(); - refetchVerifyRecords(); - }); - } + updateRecords({ + text: [ + { + key: key, + value: value, + }, + { + key: res.dataKey, + value: JSON.stringify(vc), + }, + ], + }).then(() => { + refetchRecords(); + refetchVerifyRecords(); + }); handleInternalOpenDialog(false); }); }} diff --git a/packages/@justverified/plugin/src/lib/dialogs/JustVerifiedDialog/index.tsx b/packages/@justverified/plugin/src/lib/dialogs/JustVerifiedDialog/index.tsx index 9ee70eb8..fddcd0d9 100644 --- a/packages/@justverified/plugin/src/lib/dialogs/JustVerifiedDialog/index.tsx +++ b/packages/@justverified/plugin/src/lib/dialogs/JustVerifiedDialog/index.tsx @@ -5,7 +5,6 @@ import { JustaNameDialog, JustWeb3Context, useJustWeb3, - useMApps, } from '@justweb3/widget'; import { FC, Fragment, useContext, useEffect, useState } from 'react'; import { EmailCredentialItem } from '../../components/EmailCredentialItem'; @@ -47,7 +46,6 @@ export const JustVerifiedDialog: FC = ({ selectedCredential, ]); const { connectedEns, updateRecords } = useJustWeb3(); - const { mAppsAlreadyEnabled } = useMApps(); const { refetchRecords } = useRecords({ ens: connectedEns?.ens || '', }); @@ -203,35 +201,21 @@ export const JustVerifiedDialog: FC = ({ socialValue = ''; } } - if (mAppsAlreadyEnabled?.includes(mApp)) { - updateRecords({ - text: [ - { - key: socialKey, - value: socialValue, - }, - ], - }).then(() => { - refetchRecords(); - refetchVerifyRecords(); - }); - } else { - updateRecords({ - text: [ - { - key: socialKey, - value: socialValue, - }, - { - key: credentialKey, - value: JSON.stringify(credentialValue), - }, - ], - }).then(() => { - refetchRecords(); - refetchVerifyRecords(); - }); - } + updateRecords({ + text: [ + { + key: socialKey, + value: socialValue, + }, + { + key: credentialKey, + value: JSON.stringify(credentialValue), + }, + ], + }).then(() => { + refetchRecords(); + refetchVerifyRecords(); + }); }) .catch((error) => { setSelectedCredential(undefined); @@ -263,7 +247,6 @@ export const JustVerifiedDialog: FC = ({ ) : ( { pluginApi.setState('verificationOpen', false); }, - onMAppAdd: (pluginApi, ens, mApp) => { - pluginApi.setState('verificationOpen', true); - }, }, }); diff --git a/packages/@justweb3/ui/src/lib/icons/components/general/Mapp.tsx b/packages/@justweb3/ui/src/lib/icons/components/general/Mapp.tsx deleted file mode 100644 index 9797920a..00000000 --- a/packages/@justweb3/ui/src/lib/icons/components/general/Mapp.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { SVGProps } from 'react'; -export default function Mapp(props: SVGProps) { - return ( - - - - - - - - - ); -} diff --git a/packages/@justweb3/ui/src/lib/icons/components/general/index.ts b/packages/@justweb3/ui/src/lib/icons/components/general/index.ts index d78fdac0..bd8539f2 100644 --- a/packages/@justweb3/ui/src/lib/icons/components/general/index.ts +++ b/packages/@justweb3/ui/src/lib/icons/components/general/index.ts @@ -29,7 +29,6 @@ import GeneralIcon from './General'; import LocationOnIcon from './LocationOn'; import LocationIcon from './Location'; import LogoutIcon from './Logout'; -import MappIcon from './Mapp'; import MaximizeIcon from './Maximize'; import MicIcon from './Mic'; import MinimizeIcon from './Minimize'; @@ -87,7 +86,6 @@ const general = { 'location-on': LocationOnIcon, location: LocationIcon, logout: LogoutIcon, - mapp: MappIcon, maximize: MaximizeIcon, mic: MicIcon, minimize: MinimizeIcon, @@ -148,7 +146,6 @@ export { LocationOnIcon, LocationIcon, LogoutIcon, - MappIcon, MaximizeIcon, MicIcon, MinimizeIcon, diff --git a/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx b/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx index 6d716993..a23eaf42 100644 --- a/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx +++ b/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx @@ -2,8 +2,6 @@ import { Records, useAccountEnsNames, useAccountSubnames, - useCanEnableMApps, - useEnabledMApps, useEnsAvatar, useMountedAccount, useOffchainResolvers, @@ -21,20 +19,17 @@ import { formatText, LoadingSpinner, LogoutIcon, - MappIcon, P, Popover, PopoverTrigger, ProfileIcon, SettingsIcon, SwitchAccountIcon, - SPAN, } from '@justweb3/ui'; import { FC, ReactNode, useContext, useMemo, useState } from 'react'; import { useDisconnect } from 'wagmi'; import { ConfigurationDialog, PrimaryNamesDialog } from '../../dialogs'; import { DefaultDialog } from '../../dialogs/DefaultDialog'; -import { MAppsDialog } from '../../dialogs/MAppsDialog'; import { getChainIcon } from '../../icons/chain-icons'; import { getTextRecordIcon } from '../../icons/records-icons'; import { JustWeb3Context, useJustWeb3 } from '../../providers'; @@ -57,11 +52,10 @@ export const JustWeb3Button: FC = ({ logout, style, }) => { - const [openMApps, setOpenMApps] = useState(false); const [openSettings, setOpenSettings] = useState(false); const [openPrimaryNames, setOpenPrimaryNames] = useState(false); const [openConfiguration, setOpenConfiguration] = useState(false); - const { plugins, mApps, config } = useContext(JustWeb3Context); + const { plugins, config } = useContext(JustWeb3Context); const { createPluginApi } = useContext(PluginContext); const { address, isConnected, chainId } = useMountedAccount(); const [mobileDialogOpen, setMobileDialogOpen] = useState(false); @@ -74,22 +68,10 @@ export const JustWeb3Button: FC = ({ handleOpenSignInDialog, openEnsProfile, } = useJustWeb3(); - const { canEnableMApps, isCanEnableMAppsPending } = useCanEnableMApps({ - ens: connectedEns?.ens || '', - }); const { offchainResolvers } = useOffchainResolvers(); - const { enabledMApps } = useEnabledMApps({ - ens: connectedEns?.ens || '', - }); const { records, isRecordsPending } = useRecords({ ens: connectedEns?.ens }); - const mAppsToEnable = useMemo(() => { - if (!mApps || !enabledMApps) { - return undefined; - } - return mApps.filter((mApp) => !enabledMApps.includes(mApp)); - }, [mApps, enabledMApps]); const { avatar } = useEnsAvatar({ ens: connectedEns?.ens, @@ -129,12 +111,6 @@ export const JustWeb3Button: FC = ({ ); }, [records]); - const handleOpenMAppsDialog = (open: boolean) => { - if (open !== openMApps) { - setOpenMApps(open); - } - }; - const handleOpenPrimaryNamesDialog = (open: boolean) => { if (open !== openPrimaryNames) { setOpenPrimaryNames(open); @@ -482,37 +458,6 @@ export const JustWeb3Button: FC = ({ ); })} - } - title={'mApps'} - style={{ - width: '100%', - display: 'none', - }} - onClick={() => setOpenMApps(true)} - right={ - - {mAppsToEnable && canEnableMApps && mAppsToEnable.length > 0 && ( - - Configuration Required - - )} - - - } - disabled={!canEnableMApps} - loading={isCanEnableMAppsPending} - /> - = ({ return ( <> -
diff --git a/packages/@justweb3/widget/src/lib/dialogs/AuthorizeMAppDialog/index.tsx b/packages/@justweb3/widget/src/lib/dialogs/AuthorizeMAppDialog/index.tsx deleted file mode 100644 index ad14cf91..00000000 --- a/packages/@justweb3/widget/src/lib/dialogs/AuthorizeMAppDialog/index.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import { FC, Fragment, useEffect, useMemo, useState } from 'react'; -import { - useAddMAppPermission, - useCanEnableMApps, - useIsMAppEnabled, - useRecords, -} from '@justaname.id/react'; -import { - Badge, - Button, - ClickableItem, - Flex, - H2, - JustaNameLogoIcon, - P, - SPAN, -} from '@justweb3/ui'; -import { isParseable } from '../../utils'; -import { DefaultDialog } from '../DefaultDialog'; - -export interface AuthorizeMAppDialogProps { - mApp: { - name: string; - isOpen: boolean; - }; - handleOpenDialog: (open: boolean) => void; - logo?: string; - isLoggedIn: boolean; - handleOpenSignInDialog: (open: boolean) => void; - connectedEns: string | undefined; - isEnsAuthPending: boolean; - disableOverlay?: boolean; -} - -export const AuthorizeMAppDialog: FC = ({ - mApp: { name: mApp, isOpen: open }, - handleOpenDialog, - logo, - isLoggedIn, - handleOpenSignInDialog, - connectedEns, - isEnsAuthPending, - disableOverlay, -}) => { - const [openOnConnect] = useState(open); - const { records: mAppRecords, isRecordsPending: isMAppRecordsPending } = - useRecords({ - ens: mApp || '', - }); - const { records } = useRecords({ - ens: connectedEns || '', - }); - const { canEnableMApps, isCanEnableMAppsPending } = useCanEnableMApps({ - ens: connectedEns || '', - }); - const { isMAppEnabled, isMAppEnabledPending } = useIsMAppEnabled({ - ens: connectedEns || '', - mApp, - }); - const { addMAppPermission, isAddMAppPermissionPending } = - useAddMAppPermission({ - mApp, - }); - - const mAppFieldsInEnsRecords = useMemo(() => { - return records?.records.texts?.filter((text) => - text.key.endsWith(`_${mApp}`) - ); - }, [records, mApp]); - - const mAppDescription = useMemo(() => { - return mAppRecords?.records.texts?.find( - (text) => text.key === `mApp_description` - )?.value; - }, [mAppRecords]); - - const mAppPermissions = useMemo((): string[] => { - if (!mAppRecords) { - return []; - } - - const permissions = mAppRecords?.records.texts?.find( - (text) => text.key === `mApp_permissions` - )?.value; - if (!permissions) { - return []; - } - - if (!isParseable(permissions)) { - return []; - } - - const parsedPermissions = JSON.parse(permissions) as string[]; - if (!Array.isArray(parsedPermissions)) { - return []; - } - - return parsedPermissions; - }, [mAppRecords]); - - const handleOpenDialogInternal = (_open: boolean) => { - if (!connectedEns) { - handleOpenSignInDialog(true); - return; - } - - if (_open !== open) { - handleOpenDialog(_open); - } - }; - - useEffect(() => { - if (connectedEns) { - if (isCanEnableMAppsPending) { - return; - } - - if (!canEnableMApps && canEnableMApps !== undefined) { - handleOpenDialogInternal(false); - return; - } - if (isLoggedIn) { - if (isMAppEnabledPending || isMAppEnabled === undefined) { - return; - } - handleOpenDialogInternal(!isMAppEnabled && openOnConnect); - } else { - handleOpenDialogInternal(false); - } - } - }, [ - isMAppEnabled, - isLoggedIn, - isMAppEnabledPending, - connectedEns, - isCanEnableMAppsPending, - canEnableMApps, - ]); - - if (isEnsAuthPending || !connectedEns || !records) { - return null; - } - - if ( - (isMAppRecordsPending || isCanEnableMAppsPending || isMAppEnabledPending) && - open - ) { - return null; - } - - return ( - handleOpenDialogInternal(false)} - header={ -
- {logo ? ( - logo - ) : ( - - )} -
- } - disableOverlay={disableOverlay} - > - - - - - {connectedEns} - - - -

Authorise {mApp} mApp

-
-
- - - {!mAppDescription && !mAppPermissions.length && ( -

- This mApp does not have any permissions or description, be - cautious when authorising it. -

- )} - - {mAppDescription && ( - -

{mAppDescription}

-
- )} - - {mAppPermissions?.length > 0 || - (mAppFieldsInEnsRecords && mAppFieldsInEnsRecords?.length > 0) ? ( - - {mAppPermissions.map((permission, index) => { - return ( - - -

Permission

- -

{permission}

-
-
- ); - })} - - {mAppFieldsInEnsRecords && mAppFieldsInEnsRecords.length > 0 && ( - <> -

Installing this mApp will remove the following fields:

- - {mAppFieldsInEnsRecords.map((field) => { - return ( - - - - ); - })} - - - )} -
- ) : null} -
- -
-
- ); -}; diff --git a/packages/@justweb3/widget/src/lib/dialogs/MAppsDialog/index.tsx b/packages/@justweb3/widget/src/lib/dialogs/MAppsDialog/index.tsx deleted file mode 100644 index ebbc8b34..00000000 --- a/packages/@justweb3/widget/src/lib/dialogs/MAppsDialog/index.tsx +++ /dev/null @@ -1,268 +0,0 @@ -import { FC, Fragment, useContext, useEffect, useMemo, useState } from 'react'; -import { useEnabledMApps, useEnsAvatar, useRecords } from '@justaname.id/react'; -import { - ArrowIcon, - Avatar, - Badge, - Button, - ClickableItem, - Divider, - Flex, - H2, - JustaNameLogoIcon, - LoadingSpinner, - P, - SPAN, - TrashIcon, -} from '@justweb3/ui'; -import { DefaultDialog } from '../DefaultDialog'; -import { JustWeb3Context, useJustWeb3 } from '../../providers'; -import { useMApps } from '../../providers/MAppProvider'; - -export interface AuthorizeMAppDialogProps { - open: boolean; - handleOpenDialog: (open: boolean) => void; -} - -export const MAppsDialog: FC = ({ - open, - handleOpenDialog, -}) => { - const { - config: { logo, disableOverlay }, - mApps, - } = useContext(JustWeb3Context); - const { handleOpenRevokeMAppDialog, handleOpenAuthorizeMAppDialog } = - useMApps(); - const { connectedEns, isEnsAuthPending } = useJustWeb3(); - const { getRecords } = useRecords(); - const { enabledMApps, isMAppEnabledPending } = useEnabledMApps({ - ens: connectedEns?.ens || '', - }); - const { getEnsAvatar } = useEnsAvatar(); - const [mAppsDescription, setMAppsDescription] = useState< - { mApp: string; description: string }[] | undefined - >(undefined); - const [mAppsAvatar, setMAppsAvatar] = useState< - { mApp: string; avatar: string }[] | undefined - >(undefined); - const mAppsToEnable = useMemo(() => { - if (!mApps || !enabledMApps) { - return undefined; - } - return mApps.filter((mApp) => !enabledMApps.includes(mApp)); - }, [mApps, enabledMApps]); - - const mAppsAlreadyEnabled = useMemo(() => { - if (!mApps || !enabledMApps) { - return undefined; - } - return mApps.filter((mApp) => enabledMApps.includes(mApp)); - }, [mApps, enabledMApps]); - - useEffect(() => { - if (!mApps) { - return; - } - Promise.allSettled(mApps.map((mApp) => getRecords({ ens: mApp }))).then( - (records) => { - setMAppsDescription( - records - ?.filter((record) => record.status === 'fulfilled') - .map((record) => record.value) - .map((record, index) => ({ - mApp: mApps[index], - description: - record?.records?.texts.find( - (text) => text.key === `mApp_description` - )?.value || '', - })) - ); - } - ); - - Promise.allSettled(mApps.map((mApp) => getEnsAvatar({ name: mApp }))).then( - (avatars) => { - setMAppsAvatar( - avatars - ?.filter((avatar) => avatar.status === 'fulfilled') - .map((avatar) => avatar.value) - .map((avatar, index) => ({ - mApp: mApps[index], - avatar: avatar || '', - })) - ); - } - ); - }, [mApps]); - - // if (!connectedEns || isEnsAuthPending || isMAppEnabledPending) { - // return ; - // } - - return ( - handleOpenDialog(false)} - header={ -
- {logo ? ( - logo - ) : ( - - )} -
- } - > - {!connectedEns || isEnsAuthPending || isMAppEnabledPending ? ( -
- -
- ) : ( - - - - {connectedEns.ens} - - - -

mApps Configuration

-
- - - {mAppsToEnable && mAppsToEnable.length > 0 && ( - -

Installable mApps

- - - {mAppsToEnable.map((mApp, index) => { - return ( - - avatar.mApp === mApp - )?.avatar - } - size={34} - /> - } - style={{ - width: '100%', - }} - subtitle={ - mAppsDescription?.find( - (description) => description.mApp === mApp - )?.description - } - clickable={false} - right={ - - } - /> - - ); - })} - -
- )} - - {mAppsToEnable && - mAppsToEnable.length > 0 && - mAppsAlreadyEnabled && - mAppsAlreadyEnabled.length > 0 ? ( - - ) : null} - - {mAppsAlreadyEnabled && mAppsAlreadyEnabled.length > 0 && ( - -

Configured mApps

- - - {mAppsAlreadyEnabled.map((mApp, index) => { - return ( - - avatar.mApp === mApp - )?.avatar - } - size={34} - /> - } - subtitle={ - mAppsDescription?.find( - (description) => description.mApp === mApp - )?.description - } - clickable={false} - style={{ - width: '100%', - }} - right={ - - } - /> - - ); - })} - -
- )} -
-
- )} -
- ); -}; diff --git a/packages/@justweb3/widget/src/lib/dialogs/RevokeMAppDialog/index.tsx b/packages/@justweb3/widget/src/lib/dialogs/RevokeMAppDialog/index.tsx deleted file mode 100644 index 5d3e83db..00000000 --- a/packages/@justweb3/widget/src/lib/dialogs/RevokeMAppDialog/index.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import { FC, Fragment, useEffect, useMemo, useState } from 'react'; -import { - useCanEnableMApps, - useIsMAppEnabled, - useRecords, - useRevokeMAppPermission, -} from '@justaname.id/react'; -import { - Badge, - Button, - ClickableItem, - Flex, - H2, - JustaNameLogoIcon, - P, - SPAN, - TrashWhiteIcon, -} from '@justweb3/ui'; -import { DefaultDialog } from '../DefaultDialog'; - -export interface RevokeMAppDialogProps { - mApp: { - name: string; - isOpen: boolean; - }; - handleOpenDialog: (open: boolean) => void; - logo?: string; - isLoggedIn: boolean; - handleOpenSignInDialog: (open: boolean) => void; - connectedEns: string | undefined; - isEnsAuthPending: boolean; - disableOverlay?: boolean; -} - -export const RevokeMAppDialog: FC = ({ - mApp: { name: mApp, isOpen: open }, - handleOpenDialog, - logo, - isLoggedIn, - handleOpenSignInDialog, - connectedEns, - isEnsAuthPending, - disableOverlay, -}) => { - const [openOnConnect] = useState(open); - const { records: mAppRecords, isRecordsPending: isMAppRecordsPending } = - useRecords({ - ens: mApp || '', - }); - const { records } = useRecords({ - ens: connectedEns || '', - }); - const { canEnableMApps, isCanEnableMAppsPending } = useCanEnableMApps({ - ens: connectedEns || '', - }); - const { isMAppEnabled, isMAppEnabledPending } = useIsMAppEnabled({ - ens: connectedEns || '', - mApp, - }); - const { revokeMAppPermission, isRevokeMAppPermissionPending } = - useRevokeMAppPermission({ - mApp, - }); - - const mAppDescription = useMemo(() => { - return mAppRecords?.records.texts?.find( - (text) => text.key === `mApp_description` - )?.value; - }, [mAppRecords]); - - const mAppFieldsInEnsRecords = useMemo(() => { - return records?.records.texts?.filter((text) => - text.key.endsWith(`_${mApp}`) - ); - }, [records, mApp]); - - const handleOpenDialogInternal = (_open: boolean) => { - if (!connectedEns) { - handleOpenSignInDialog(true); - return; - } - - if (_open !== open) { - handleOpenDialog(_open); - } - }; - - useEffect(() => { - if (connectedEns) { - if (isCanEnableMAppsPending) { - return; - } - - if (!canEnableMApps && canEnableMApps !== undefined) { - handleOpenDialogInternal(false); - return; - } - if (isLoggedIn) { - if (isMAppEnabledPending || isMAppEnabled === undefined) { - return; - } - handleOpenDialogInternal(!isMAppEnabled && openOnConnect); - } else { - handleOpenDialogInternal(false); - } - } - }, [ - isMAppEnabled, - isLoggedIn, - isMAppEnabledPending, - connectedEns, - isCanEnableMAppsPending, - canEnableMApps, - ]); - - if (isEnsAuthPending || !connectedEns || !records) { - return null; - } - - if ( - (isMAppRecordsPending || isCanEnableMAppsPending || isMAppEnabledPending) && - open - ) { - return null; - } - - return ( - handleOpenDialogInternal(false)} - header={ -
- {logo ? ( - logo - ) : ( - - )} -
- } - disableOverlay={disableOverlay} - > - - - - - {connectedEns} - - - - -

Revoke {mApp} mApp

-
-
- - -

{mAppDescription}

-
- - -

Removing this mApp will revoke all permissions granted to it.

- {mAppFieldsInEnsRecords && mAppFieldsInEnsRecords.length > 0 && ( - <> -

The following fields will be removed:

- - {mAppFieldsInEnsRecords.map((field) => { - return ( - - - - ); - })} - - - )} -
- - - - - - -
-
- ); -}; diff --git a/packages/@justweb3/widget/src/lib/dialogs/index.ts b/packages/@justweb3/widget/src/lib/dialogs/index.ts index 34a8d186..d79a6e96 100644 --- a/packages/@justweb3/widget/src/lib/dialogs/index.ts +++ b/packages/@justweb3/widget/src/lib/dialogs/index.ts @@ -1,4 +1,3 @@ -export * from './AuthorizeMAppDialog'; export * from './AvatarSelectorDialog'; export * from './BannerSelectorDialog'; export * from './ConfigurationDialog'; diff --git a/packages/@justweb3/widget/src/lib/plugins/index.ts b/packages/@justweb3/widget/src/lib/plugins/index.ts index 5ba874b9..25e3ec05 100644 --- a/packages/@justweb3/widget/src/lib/plugins/index.ts +++ b/packages/@justweb3/widget/src/lib/plugins/index.ts @@ -10,15 +10,12 @@ export interface PluginApi { isLoggedIn: boolean; chainId: number | undefined; records: UseRecordsResult['records']; - mApps: string[]; setState: (key: string, value: T) => void; getState: (key: string) => T | undefined; config: JustWeb3ProviderConfig; eventEmitter: EventEmitter; - handleOpenAuthorizeMAppDialog: (mApp: string, open: boolean) => void; - handleOpenRevokeMAppDialog: (mApp: string, open: boolean) => void; handleOpenSignInDialog: (open: boolean) => void; } @@ -74,30 +71,18 @@ type OnEnsSignInHook = ( pluginApi: PluginApi, ens: string, chainId: number, - records: UseRecordsResult['records'], - enabledMApps: string[], - canEnableMApps: boolean + records: UseRecordsResult['records'] ) => void; type OnEnsChangeHook = ( pluginApi: PluginApi, ens: string, - records: UseRecordsResult['records'], - enabledMApps: string[], - canEnableMApps: boolean + records: UseRecordsResult['records'] ) => void; type OnEnsSignOutHook = (pluginApi: PluginApi, ens: string) => void; -type OnMAppAddHook = (pluginApi: PluginApi, ens: string, mApp: string) => void; -type OnMAppRemoveHook = ( - pluginApi: PluginApi, - ens: string, - mApp: string -) => void; type OnRecordsChangeHook = ( pluginApi: PluginApi, ens: string, - records: UseRecordsResult['records'], - enabledMApps: string[], - canEnableMApps: boolean + records: UseRecordsResult['records'] ) => void; type OnSubnameClaimedHook = (pluginApi: PluginApi, subname: string) => void; type OnSwitchChain = ( @@ -117,8 +102,6 @@ interface Hooks { onSwitchChain?: OnSwitchChain; onSubnameClaimed?: OnSubnameClaimedHook; onEnsChange?: OnEnsChangeHook; - onMAppAdd?: OnMAppAddHook; - onMAppRemove?: OnMAppRemoveHook; onRecordsChange?: OnRecordsChangeHook; onStateChange?: ( pluginApi: PluginApi, @@ -133,8 +116,6 @@ export interface JustaPlugin { components?: PluginComponents; - mApps?: string[]; - hooks?: Hooks; priority?: number; diff --git a/packages/@justweb3/widget/src/lib/providers/JustWeb3Provider/index.tsx b/packages/@justweb3/widget/src/lib/providers/JustWeb3Provider/index.tsx index 85c80f8c..5a955dd4 100644 --- a/packages/@justweb3/widget/src/lib/providers/JustWeb3Provider/index.tsx +++ b/packages/@justweb3/widget/src/lib/providers/JustWeb3Provider/index.tsx @@ -25,7 +25,7 @@ import { } from '@justaname.id/react'; import { JustWeb3ThemeProvider } from '@justweb3/ui'; import { SignInDialog } from '../../dialogs/SignInDialog'; -import { MAppsProvider } from '../MAppProvider'; +import { PluginProvider } from '../PluginProvider'; import { JustaPlugin } from '../../plugins'; import usePreviousState from '../../hooks/usePreviousState'; import { ProfileDialog, UpdateRecordDialog } from '../../dialogs'; @@ -51,7 +51,6 @@ export interface JustWeb3ContextProps { isSignInOpen: boolean; config: JustWeb3ProviderConfig; plugins: JustaPlugin[]; - mApps: string[]; } export const JustWeb3Context = createContext({ @@ -63,7 +62,6 @@ export const JustWeb3Context = createContext({ handleJustWeb3Config: () => { }, config: {}, plugins: [], - mApps: [], }); export const JustWeb3Provider: FC = ({ @@ -104,20 +102,6 @@ export const JustWeb3Provider: FC = ({ [config.plugins] ); - const pluginsMApps = - (useMemo( - () => - plugins - ?.filter((plugin) => plugin.mApps) - ?.map((plugin) => plugin.mApps) - .flat() - .map((mApp) => ({ - name: mApp, - openOnConnect: false, - })), - [plugins] - ) as { name: string; openOnConnect: boolean }[]) || []; - const handleUpdateRecords = async ( records: UpdateRecordsParams & { ens: string } ) => { @@ -142,27 +126,6 @@ export const JustWeb3Provider: FC = ({ } }, [updateRecord]); - const mAppsWithOpenOnConnect = - config?.mApps?.map((mApp) => { - if (typeof mApp === 'string') { - return { - name: mApp, - openOnConnect: false, - }; - } - return mApp; - }) || []; - - const allMApps = [...mAppsWithOpenOnConnect, ...pluginsMApps].reduce( - (acc, mApp) => { - if (!acc.find((accMApp) => accMApp.name === mApp.name)) { - return [...acc, mApp]; - } - return acc; - }, - [] as { name: string; openOnConnect: boolean }[] - ); - const handleOpenSignInDialog = (open: boolean) => { if (!isConnected) { return; @@ -214,16 +177,13 @@ export const JustWeb3Provider: FC = ({ isSignInOpen: signInOpen, config: config, plugins, - mApps: allMApps.map((mApp) => mApp.name), handleUpdateRecords: handleUpdateRecords, handleJustWeb3Config, handleOpenEnsProfile, handleCloseEnsProfile, }} > - = ({ logo={config.logo} /> {children} - + diff --git a/packages/@justweb3/widget/src/lib/providers/MAppProvider/index.tsx b/packages/@justweb3/widget/src/lib/providers/MAppProvider/index.tsx deleted file mode 100644 index 56cac1bb..00000000 --- a/packages/@justweb3/widget/src/lib/providers/MAppProvider/index.tsx +++ /dev/null @@ -1,322 +0,0 @@ -import { - createContext, - FC, - Fragment, - ReactNode, - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from 'react'; -import { - useCanEnableMApps, - useEnabledMApps, - useEnsAuth, - useIsMAppEnabled, -} from '@justaname.id/react'; -import { AuthorizeMAppDialog } from '../../dialogs/AuthorizeMAppDialog'; -import { PluginProvider } from '../PluginProvider'; -import { JustaPlugin } from '../../plugins'; -import { RevokeMAppDialog } from '../../dialogs/RevokeMAppDialog'; -import { JustWeb3ProviderConfig } from '../../types/config'; - -export interface MApp { - name: string; - isOpen: boolean; -} - -export interface MAppContextProps { - mAppsToEnable: string[] | undefined; - mAppsAlreadyEnabled: string[] | undefined; - canEnableMApps: boolean | undefined; - handleOpenAuthorizeMAppDialog: (mAppName: string, open?: boolean) => void; - handleOpenRevokeMAppDialog: (mAppName: string, open?: boolean) => void; - handleOpenSignInDialog: (open: boolean) => void; - config: JustWeb3ProviderConfig; -} - -export const MAppContext = createContext({ - mAppsToEnable: undefined, - mAppsAlreadyEnabled: undefined, - canEnableMApps: undefined, - handleOpenAuthorizeMAppDialog: () => {}, - handleOpenRevokeMAppDialog: () => {}, - handleOpenSignInDialog: () => {}, - config: {}, -}); - -interface MAppsProviderProps { - logo?: string; - handleOpenSignInDialog: (open: boolean) => void; - children: ReactNode; - mApps?: { - name: string; - openOnConnect: boolean; - }[]; - plugins: JustaPlugin[]; - disableOverlay?: boolean; - config: JustWeb3ProviderConfig; -} - -export const MAppsProvider: FC = ({ - logo, - handleOpenSignInDialog, - children, - disableOverlay, - mApps: initialMApps = [], - plugins, - config, -}) => { - const { isEnsAuthPending, isLoggedIn, connectedEns } = useEnsAuth({ - local: !config.enableAuth, - }); - const [mAppsToEnableOpen, setMAppsToEnableOpen] = useState< - { name: string; isOpen: boolean }[] | undefined - >(undefined); - const [mAppsAlreadyEnabledOpen, setMAppsAlreadyEnabledOpen] = useState< - { name: string; isOpen: boolean }[] | undefined - >(undefined); - - const { canEnableMApps } = useCanEnableMApps({ - ens: connectedEns?.ens || '', - }); - const { enabledMApps } = useEnabledMApps({ - ens: connectedEns?.ens || '', - }); - - const mAppsToEnable = useMemo(() => { - if (!initialMApps || !enabledMApps) { - return undefined; - } - return initialMApps.filter((mApp) => !enabledMApps.includes(mApp.name)); - }, [initialMApps, enabledMApps]); - - const mAppsAlreadyEnabled = useMemo(() => { - if (!initialMApps || !enabledMApps) { - return undefined; - } - return initialMApps.filter((mApp) => enabledMApps.includes(mApp.name)); - }, [initialMApps, enabledMApps]); - - useEffect(() => { - if (!mAppsToEnable) { - return; - } - setMAppsToEnableOpen( - mAppsToEnable.map((mApp) => ({ - name: mApp.name, - isOpen: mApp.openOnConnect, - })) - ); - }, [mAppsToEnable]); - - useEffect(() => { - if (!mAppsAlreadyEnabled) { - return; - } - setMAppsAlreadyEnabledOpen( - mAppsAlreadyEnabled.map((mApp) => ({ name: mApp.name, isOpen: false })) - ); - }, [mAppsAlreadyEnabled]); - - const handleOpenAuthorizeMAppDialog = useCallback( - (mAppName: string, open = true) => { - setMAppsToEnableOpen((prev) => - prev?.map((mApp) => { - if (mApp.name === mAppName) { - return { - ...mApp, - isOpen: open, - }; - } - return mApp; - }) - ); - }, - [] - ); - - const handleOpenRevokeMAppDialog = useCallback( - (mAppName: string, open = true) => { - setMAppsAlreadyEnabledOpen((prev) => - prev?.map((mApp) => { - if (mApp.name === mAppName) { - return { - ...mApp, - isOpen: open, - }; - } - return mApp; - }) - ); - }, - [] - ); - - return ( - mApp.name), - mAppsAlreadyEnabled: mAppsAlreadyEnabled?.map((mApp) => mApp.name), - handleOpenAuthorizeMAppDialog, - handleOpenRevokeMAppDialog, - canEnableMApps, - handleOpenSignInDialog, - config, - }} - > - mApp.name)} - plugins={plugins} - handleOpenSignInDialog={handleOpenSignInDialog} - handleOpenAuthorizeMAppDialog={handleOpenAuthorizeMAppDialog} - handleOpenRevokeMAppDialog={handleOpenRevokeMAppDialog} - config={config} - > - {mAppsToEnableOpen && - mAppsToEnableOpen.map((mApp) => ( - - - handleOpenAuthorizeMAppDialog(mApp.name, open) - } - mApp={mApp} - logo={logo} - handleOpenSignInDialog={handleOpenSignInDialog} - connectedEns={connectedEns?.ens} - isEnsAuthPending={isEnsAuthPending} - isLoggedIn={isLoggedIn} - disableOverlay={disableOverlay} - /> - - ))} - {mAppsAlreadyEnabledOpen && - mAppsAlreadyEnabledOpen.map((mApp) => ( - - - handleOpenRevokeMAppDialog(mApp.name, open) - } - mApp={mApp} - logo={logo} - handleOpenSignInDialog={handleOpenSignInDialog} - connectedEns={connectedEns?.ens} - isEnsAuthPending={isEnsAuthPending} - isLoggedIn={isLoggedIn} - disableOverlay={disableOverlay} - /> - - ))} - {children} - - - ); -}; - -interface UseMAppParams { - mApp: string; -} - -interface UseMAppResult { - handleOpenAuthorizeMAppDialog: (open: boolean) => void; - handleOpenRevokeMAppDialog: (open: boolean) => void; - isMAppEnabled: boolean | undefined; - canOpenMAppDialog: boolean; - isPending: boolean; -} - -export const useMApp = ({ mApp }: UseMAppParams): UseMAppResult => { - const { - handleOpenAuthorizeMAppDialog: contextOpenAuthorizeMAppDialog, - handleOpenRevokeMAppDialog: contextOpenRevokeMAppDialog, - handleOpenSignInDialog, - config, - } = useContext(MAppContext); - const { connectedEns } = useEnsAuth({ - local: !config.enableAuth, - }); - const { isMAppEnabled, isMAppEnabledPending } = useIsMAppEnabled({ - ens: connectedEns?.ens || '', - mApp, - }); - const { canEnableMApps, isCanEnableMAppsPending } = useCanEnableMApps({ - ens: connectedEns?.ens || '', - }); - - const isPending = isMAppEnabledPending || isCanEnableMAppsPending; - - const handleOpenAuthorizeMAppDialog = useCallback( - (open: boolean) => { - if (!connectedEns) { - handleOpenSignInDialog(true); - return; - } - - if (isPending) { - return; - } - - if (!isMAppEnabled && canEnableMApps) { - contextOpenAuthorizeMAppDialog(mApp, open); - } - }, - [ - connectedEns, - handleOpenSignInDialog, - isPending, - isMAppEnabled, - canEnableMApps, - contextOpenAuthorizeMAppDialog, - mApp, - ] - ); - - const handleOpenRevokeMAppDialog = useCallback( - (open: boolean) => { - if (!connectedEns) { - handleOpenSignInDialog(true); - return; - } - - if (isPending) { - return; - } - - if (isMAppEnabled) { - contextOpenRevokeMAppDialog(mApp, open); - } - }, - [ - connectedEns, - handleOpenSignInDialog, - isPending, - isMAppEnabled, - contextOpenRevokeMAppDialog, - mApp, - ] - ); - - const canOpenMAppDialog = useMemo(() => { - if (!connectedEns) { - return true; - } - return !isMAppEnabled && canEnableMApps && !isPending; - }, [connectedEns, isMAppEnabled, canEnableMApps, isPending]); - - return { - handleOpenAuthorizeMAppDialog, - handleOpenRevokeMAppDialog, - isMAppEnabled, - canOpenMAppDialog: Boolean(canOpenMAppDialog), - isPending, - }; -}; - -export const useMApps = (): MAppContextProps => { - const context = useContext(MAppContext); - if (context === undefined) { - throw new Error('useMApps must be used within a MAppsProvider'); - } - return context; -}; diff --git a/packages/@justweb3/widget/src/lib/providers/PluginProvider/index.tsx b/packages/@justweb3/widget/src/lib/providers/PluginProvider/index.tsx index 5e4d8743..cf5cb4c0 100644 --- a/packages/@justweb3/widget/src/lib/providers/PluginProvider/index.tsx +++ b/packages/@justweb3/widget/src/lib/providers/PluginProvider/index.tsx @@ -8,8 +8,6 @@ import React, { useState, } from 'react'; import { - useCanEnableMApps, - useEnabledMApps, useEnsAuth, useMountedAccount, useRecords, @@ -34,9 +32,6 @@ interface PluginProviderProps { children: React.ReactNode; plugins: JustaPlugin[]; handleOpenSignInDialog: (open: boolean) => void; - handleOpenAuthorizeMAppDialog: (mAppName: string, open: boolean) => void; - handleOpenRevokeMAppDialog: (mAppName: string, open: boolean) => void; - mApps?: string[]; config: JustWeb3ProviderConfig; } @@ -44,9 +39,6 @@ export const PluginProvider: FC = ({ children, plugins, handleOpenSignInDialog, - handleOpenAuthorizeMAppDialog, - handleOpenRevokeMAppDialog, - mApps, config, }) => { const { connectedEns, isEnsAuthPending, isLoggedIn } = useEnsAuth({ @@ -55,23 +47,14 @@ export const PluginProvider: FC = ({ const { records } = useRecords({ ens: connectedEns?.ens || '', }); - const { enabledMApps } = useEnabledMApps({ - ens: connectedEns?.ens || '', - }); - const { canEnableMApps } = useCanEnableMApps({ - ens: connectedEns?.ens || '', - }); const { address, chain } = useMountedAccount(); const previousConnectedEns = usePreviousState(connectedEns, [ connectedEns, records, - enabledMApps, - canEnableMApps, ]); const previousChain = usePreviousState(chain, [chain]); const previousAddress = usePreviousState(address, [address]); const previousRecords = usePreviousState(records, [records]); - const previousEnabledMApps = usePreviousState(enabledMApps, [enabledMApps]); const [pluginStates, setPluginStates] = useState< Record> @@ -84,11 +67,8 @@ export const PluginProvider: FC = ({ connectedEns, isEnsAuthPending, isLoggedIn, - mApps: mApps || [], chainId: chain?.id, handleOpenSignInDialog, - handleOpenAuthorizeMAppDialog, - handleOpenRevokeMAppDialog, records, config, eventEmitter: new EventEmitter(), @@ -110,9 +90,6 @@ export const PluginProvider: FC = ({ isEnsAuthPending, isLoggedIn, handleOpenSignInDialog, - handleOpenAuthorizeMAppDialog, - handleOpenRevokeMAppDialog, - mApps, eventEmitter, pluginStates, records, @@ -174,8 +151,6 @@ export const PluginProvider: FC = ({ if ( records && - enabledMApps !== undefined && - canEnableMApps !== undefined && connectedEns && !previousConnectedEns && plugin.hooks?.onEnsSignIn @@ -185,9 +160,7 @@ export const PluginProvider: FC = ({ pluginApi, connectedEns?.ens, chain?.id || 1, - records, - enabledMApps, - canEnableMApps + records ); } catch (error) { console.error( @@ -199,21 +172,13 @@ export const PluginProvider: FC = ({ if ( records && - enabledMApps !== undefined && - canEnableMApps !== undefined && connectedEns && previousConnectedEns && connectedEns.ens !== previousConnectedEns.ens && plugin.hooks?.onEnsChange ) { try { - plugin.hooks?.onEnsChange( - pluginApi, - connectedEns?.ens, - records, - enabledMApps, - canEnableMApps - ); + plugin.hooks?.onEnsChange(pluginApi, connectedEns?.ens, records); } catch (error) { console.error( `Error in plugin ${plugin.name} onEnsChange hook:`, @@ -233,14 +198,7 @@ export const PluginProvider: FC = ({ } } }); - }, [ - plugins, - connectedEns, - previousConnectedEns, - records, - enabledMApps, - canEnableMApps, - ]); + }, [plugins, connectedEns, previousConnectedEns, records]); useEffect(() => { plugins?.forEach((plugin) => { @@ -290,24 +248,13 @@ export const PluginProvider: FC = ({ plugins?.forEach((plugin) => { const pluginApi = createPluginApi(plugin.name); - if ( - !records || - enabledMApps === undefined || - canEnableMApps === undefined || - !connectedEns - ) { + if (!records || !connectedEns) { return; } if (!isEqual(records, previousRecords) && plugin.hooks?.onRecordsChange) { try { - plugin.hooks.onRecordsChange( - pluginApi, - connectedEns?.ens, - records, - enabledMApps, - canEnableMApps - ); + plugin.hooks.onRecordsChange(pluginApi, connectedEns?.ens, records); } catch (error) { console.error( `Error in plugin ${plugin.name} onRecordsChange hook:`, @@ -316,70 +263,7 @@ export const PluginProvider: FC = ({ } } }); - }, [ - plugins, - records, - previousRecords, - connectedEns, - enabledMApps, - canEnableMApps, - ]); - - useEffect(() => { - plugins?.forEach((plugin) => { - const pluginApi = createPluginApi(plugin.name); - - if ( - enabledMApps === undefined || - !connectedEns || - !previousEnabledMApps - ) { - return; - } - - if ( - !isEqual(enabledMApps, previousEnabledMApps) && - plugin.hooks?.onMAppAdd - ) { - const addedMApp = enabledMApps.find( - (mApp) => !previousEnabledMApps.includes(mApp) - ); - if (addedMApp) { - try { - plugin.hooks.onMAppAdd(pluginApi, connectedEns?.ens, addedMApp); - } catch (error) { - console.error( - `Error in plugin ${plugin.name} onMAppAdd hook:`, - error - ); - } - } - } - - if ( - !isEqual(enabledMApps, previousEnabledMApps) && - plugin.hooks?.onMAppRemove - ) { - const removedMApp = previousEnabledMApps.find( - (mApp) => !enabledMApps.includes(mApp) - ); - if (removedMApp) { - try { - plugin.hooks.onMAppRemove( - pluginApi, - connectedEns?.ens, - removedMApp - ); - } catch (error) { - console.error( - `Error in plugin ${plugin.name} onMAppRemove hook:`, - error - ); - } - } - } - }); - }, [plugins, enabledMApps, previousEnabledMApps, connectedEns]); + }, [plugins, records, previousRecords, connectedEns]); const globalComponentsArray: ReactNode[] = plugins?.reduce( (acc: ReactNode[], plugin: JustaPlugin) => { diff --git a/packages/@justweb3/widget/src/lib/providers/index.ts b/packages/@justweb3/widget/src/lib/providers/index.ts index ff941cf6..7250913f 100644 --- a/packages/@justweb3/widget/src/lib/providers/index.ts +++ b/packages/@justweb3/widget/src/lib/providers/index.ts @@ -1,3 +1,2 @@ export * from './JustWeb3Provider' -export { useMApps, useMApp } from './MAppProvider' export { usePlugins } from './PluginProvider' \ No newline at end of file diff --git a/packages/@justweb3/widget/src/lib/types/config/index.ts b/packages/@justweb3/widget/src/lib/types/config/index.ts index a35595c8..a1b4f569 100644 --- a/packages/@justweb3/widget/src/lib/types/config/index.ts +++ b/packages/@justweb3/widget/src/lib/types/config/index.ts @@ -10,7 +10,6 @@ export interface JustWeb3ProviderConfig logo?: string; disableOverlay?: boolean; enableAuth?: boolean; - mApps?: (string | { name: string; openOnConnect: boolean })[]; plugins?: JustaPlugin[]; onLogout?: () => void; } diff --git a/packages/@justweb3/widget/src/stories/multichain.stories.tsx b/packages/@justweb3/widget/src/stories/multichain.stories.tsx index 8378dcd0..82f50f25 100644 --- a/packages/@justweb3/widget/src/stories/multichain.stories.tsx +++ b/packages/@justweb3/widget/src/stories/multichain.stories.tsx @@ -45,7 +45,6 @@ const JustWeb3Config: JustWeb3ProviderConfig = { providerUrl: import.meta.env.STORYBOOK_APP_SEPOLIA_PROVIDER_URL, }, ], - mApps: ['justverified.eth', 'justweb3.eth'], openOnWalletConnect: false, allowedEns: 'all', dev: import.meta.env.STORYBOOK_APP_DEV === 'true', diff --git a/packages/@justweb3/widget/src/stories/signin.stories.tsx b/packages/@justweb3/widget/src/stories/signin.stories.tsx index 36d52c6d..2eaf3931 100644 --- a/packages/@justweb3/widget/src/stories/signin.stories.tsx +++ b/packages/@justweb3/widget/src/stories/signin.stories.tsx @@ -37,7 +37,6 @@ const JustWeb3Config: JustWeb3ProviderConfig = { providerUrl: import.meta.env.STORYBOOK_APP_SEPOLIA_PROVIDER_URL, }, ], - mApps: ['justverified.eth', 'justweb3.eth'], openOnWalletConnect: false, allowedEns: 'all', // dev: import.meta.env.STORYBOOK_APP_DEV === 'true', diff --git a/packages/@justweb3/xmtp-plugin/package.json b/packages/@justweb3/xmtp-plugin/package.json index 686523be..2a66d7a6 100644 --- a/packages/@justweb3/xmtp-plugin/package.json +++ b/packages/@justweb3/xmtp-plugin/package.json @@ -18,6 +18,7 @@ "@justweb3/widget": ">=0.0.95", "@tanstack/react-query": "^5.x", "react": ">=17", + "viem": "^2.48.0", "wagmi": "2.x" }, "exports": { diff --git a/packages/@justweb3/xmtp-plugin/src/lib/hooks/useClient/index.ts b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useClient/index.ts index cded1a18..b5e40845 100644 --- a/packages/@justweb3/xmtp-plugin/src/lib/hooks/useClient/index.ts +++ b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useClient/index.ts @@ -1,18 +1,16 @@ 'use client'; -import { arrayify } from '@ethersproject/bytes'; import { Client, type Signer } from '@xmtp/browser-sdk'; import { ReactionCodec } from '@xmtp/content-type-reaction'; import { AttachmentCodec } from '@xmtp/content-type-remote-attachment'; import { ReplyCodec } from '@xmtp/content-type-reply'; -import { JsonRpcSigner } from 'ethers'; import { useCallback, useContext, useRef, useState } from 'react'; import { XMTPContext } from '../../contexts/XMTPContext'; import { ReadReceiptCodec } from '@xmtp/content-type-read-receipt'; import { useAccount } from 'wagmi'; export type InitializeClientOptions = { - signer: JsonRpcSigner; + signer: Signer; }; function storeKeys(address: string, key: Uint8Array, env: string) { @@ -61,23 +59,7 @@ export const useXMTPClient = (onError?: (error: Error) => void) => { storeKeys(address ?? '', encryptionKey, env); } - // Create XMTP signer that converts ethers signatures to Uint8Array - const xmtpSigner: Signer = { - type: 'EOA', - getIdentifier: async () => { - const signerAddress = await signer.getAddress(); - return { - identifier: signerAddress, - identifierKind: 'Ethereum', - }; - }, - signMessage: async (message: string) => { - const signature = await signer.signMessage(message); - return arrayify(signature); - }, - }; - - xmtpClient = await Client.create(xmtpSigner, { + xmtpClient = await Client.create(signer, { dbEncryptionKey: encryptionKey, env, loggingLevel: diff --git a/packages/@justweb3/xmtp-plugin/src/lib/hooks/useEthersSigner/index.ts b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useEthersSigner/index.ts index d12456cf..3fd572fe 100644 --- a/packages/@justweb3/xmtp-plugin/src/lib/hooks/useEthersSigner/index.ts +++ b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useEthersSigner/index.ts @@ -1,30 +1,41 @@ 'use client'; -import { BrowserProvider, JsonRpcSigner } from 'ethers'; +import type { Signer } from '@xmtp/browser-sdk'; import { useMemo } from 'react'; -import type { Account, Chain, Client, Transport } from 'viem'; +import { hexToBytes } from 'viem'; import { useWalletClient } from 'wagmi'; -export function clientToSigner(client: Client) { - const { account, chain, transport } = client; - if (!account || !chain || !transport) return undefined; - const network = { - chainId: chain?.id, - name: chain?.name, - ensAddress: chain?.contracts?.ensRegistry?.address, - }; - const provider = new BrowserProvider(transport, network); - const signer = new JsonRpcSigner(provider, account.address); - return signer; -} +/** + * Returns an XMTP-compatible `Signer` backed by the wagmi/viem `WalletClient`. + * + * Renamed from `useEthersSigner` once the SDK dropped ethers in favor of viem. + * Old name kept as alias for backwards compatibility within this plugin. + */ +export const useXmtpSigner = ({ + chainId, +}: { chainId?: number } = {}): Signer | undefined => { + const { data: walletClient } = useWalletClient({ chainId }); -/** Hook to convert a viem Wallet Client to an ethers.js Signer. */ -export const useEthersSigner = ({ chainId }: { chainId?: number } = {}): - | JsonRpcSigner - | undefined => { - const { data: client } = useWalletClient({ chainId }); - const signer = useMemo( - () => (client ? clientToSigner(client) : undefined), - [client] - ); - return signer; + return useMemo(() => { + if (!walletClient?.account) return undefined; + const account = walletClient.account; + return { + type: 'EOA', + getIdentifier: async () => ({ + identifier: account.address, + identifierKind: 'Ethereum', + }), + signMessage: async (message: string) => { + const signature = await walletClient.signMessage({ + account, + message, + }); + return hexToBytes(signature); + }, + }; + }, [walletClient]); }; + +/** + * @deprecated Renamed to `useXmtpSigner`. This alias will be removed in the next major version. + */ +export const useEthersSigner = useXmtpSigner; diff --git a/packages/siwens/package.json b/packages/siwens/package.json index 52de44cc..29ad08cf 100644 --- a/packages/siwens/package.json +++ b/packages/siwens/package.json @@ -1,9 +1,12 @@ { "name": "siwens", "version": "0.1.52", + "dependencies": { + "punycode": "^2.3.1" + }, "peerDependencies": { - "ethers": "^5.6.8 || ^6.0.8", - "siwe": ">=2.0.0" + "siwe": ">=2.0.0", + "viem": "^2.48.0" }, "exports": { "./package.json": "./dist/package.json", diff --git a/yarn.lock b/yarn.lock index 7d3466b7..d9a439d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4472,9 +4472,8 @@ __metadata: qs: "npm:6.12.0" peerDependencies: "@tanstack/react-query": ^5.x - ethers: ^5.6.8 || ^6.0.8 react: ">=17" - viem: 2.x + viem: ^2.48.0 wagmi: 2.x languageName: unknown linkType: soft @@ -4488,18 +4487,19 @@ __metadata: jest: "npm:^29.4.1" qs: "npm:6.12.0" peerDependencies: - ethers: ^5.6.8 || ^6.0.8 siwe: ">=2.0.0" - viem: ">=2.35.0" + viem: ^2.48.0 languageName: unknown linkType: soft "@justaname.id/siwens@npm:0.0.145, @justaname.id/siwens@workspace:packages/@justaname.id/siwens": version: 0.0.0-use.local resolution: "@justaname.id/siwens@workspace:packages/@justaname.id/siwens" + dependencies: + punycode: "npm:^2.3.1" peerDependencies: - ethers: ^5.6.8 || ^6.0.8 siwe: ">=2.0.0" + viem: ^2.48.0 languageName: unknown linkType: soft @@ -4628,6 +4628,7 @@ __metadata: "@justweb3/widget": ">=0.0.95" "@tanstack/react-query": ^5.x react: ">=17" + viem: ^2.48.0 wagmi: 2.x languageName: unknown linkType: soft @@ -5564,15 +5565,6 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.2.0": - version: 1.2.0 - resolution: "@noble/curves@npm:1.2.0" - dependencies: - "@noble/hashes": "npm:1.3.2" - checksum: 10c0/0bac7d1bbfb3c2286910b02598addd33243cb97c3f36f987ecc927a4be8d7d88e0fcb12b0f0ef8a044e7307d1844dd5c49bb724bfa0a79c8ec50ba60768c97f6 - languageName: node - linkType: hard - "@noble/curves@npm:1.4.2, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -5634,13 +5626,6 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.3.2": - version: 1.3.2 - resolution: "@noble/hashes@npm:1.3.2" - checksum: 10c0/2482cce3bce6a596626f94ca296e21378e7a5d4c09597cbc46e65ffacc3d64c8df73111f2265444e36a3168208628258bbbaccba2ef24f65f58b2417638a20e7 - languageName: node - linkType: hard - "@noble/hashes@npm:1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" @@ -11646,13 +11631,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:18.15.13": - version: 18.15.13 - resolution: "@types/node@npm:18.15.13" - checksum: 10c0/6e5f61c559e60670a7a8fb88e31226ecc18a21be103297ca4cf9848f0a99049dae77f04b7ae677205f2af494f3701b113ba8734f4b636b355477a6534dbb8ada - languageName: node - linkType: hard - "@types/node@npm:18.16.9": version: 18.16.9 resolution: "@types/node@npm:18.16.9" @@ -13940,9 +13918,9 @@ __metadata: languageName: node linkType: hard -"abitype@npm:1.1.0": - version: 1.1.0 - resolution: "abitype@npm:1.1.0" +"abitype@npm:1.2.3": + version: 1.2.3 + resolution: "abitype@npm:1.2.3" peerDependencies: typescript: ">=5.0.4" zod: ^3.22.0 || ^4.0.0 @@ -13951,13 +13929,13 @@ __metadata: optional: true zod: optional: true - checksum: 10c0/99218d442951c60324fcd96a372c30d71ca8d5434cab62b95d5d80bae89e3024a445a90db323ef1fe4da0d749d86e815ca555a37719b06e6ca03ccad2116c45b + checksum: 10c0/c8740de1ae4961723a153224a52cb9a34a57903fb5c2ad61d5082b0b79b53033c9335381aa8c663c7ec213c9955a9853f694d51e95baceedef27356f7745c634 languageName: node linkType: hard -"abitype@npm:^1.0.9": - version: 1.1.1 - resolution: "abitype@npm:1.1.1" +"abitype@npm:^1.2.3": + version: 1.2.4 + resolution: "abitype@npm:1.2.4" peerDependencies: typescript: ">=5.0.4" zod: ^3.22.0 || ^4.0.0 @@ -13966,7 +13944,7 @@ __metadata: optional: true zod: optional: true - checksum: 10c0/d52fd8195cb37cdb462ba4d1817dafdba8da403eeab50f144f251748d7458a43308ee29ea46889db2969c91c074780e6d1f00f86acd22dc5772570432ee56b9c + checksum: 10c0/b420d8368f92a9bf456bc51a15866af2d8463e2397006551148e654cef9ca786a31d487e27942992c5b5b443b8a6b8adb0efff0c96d58e2ed81b23940fe86b2f languageName: node linkType: hard @@ -14096,13 +14074,6 @@ __metadata: languageName: node linkType: hard -"aes-js@npm:4.0.0-beta.5": - version: 4.0.0-beta.5 - resolution: "aes-js@npm:4.0.0-beta.5" - checksum: 10c0/444f4eefa1e602cbc4f2a3c644bc990f93fd982b148425fee17634da510586fc09da940dcf8ace1b2d001453c07ff042e55f7a0482b3cc9372bf1ef75479090c - languageName: node - linkType: hard - "agent-base@npm:6": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -19977,21 +19948,6 @@ __metadata: languageName: node linkType: hard -"ethers@npm:6.11.1": - version: 6.11.1 - resolution: "ethers@npm:6.11.1" - dependencies: - "@adraffy/ens-normalize": "npm:1.10.1" - "@noble/curves": "npm:1.2.0" - "@noble/hashes": "npm:1.3.2" - "@types/node": "npm:18.15.13" - aes-js: "npm:4.0.0-beta.5" - tslib: "npm:2.4.0" - ws: "npm:8.5.0" - checksum: 10c0/97a920e0244ba6cd1622b58a448c87f26dad20bad242777abb2e583d045bf7752218477bd7367ba6518c2a5e2b16030afff15e87b705526d0ea667498c27ac89 - languageName: node - linkType: hard - "ethers@npm:^5.7.2": version: 5.8.0 resolution: "ethers@npm:5.8.0" @@ -25436,7 +25392,6 @@ __metadata: eslint-plugin-jsx-a11y: "npm:6.7.1" eslint-plugin-react: "npm:7.32.2" eslint-plugin-react-hooks: "npm:4.6.0" - ethers: "npm:6.11.1" express: "npm:4.18.1" express-session: "npm:1.18.0" input-otp: "npm:1.2.4" @@ -25486,7 +25441,7 @@ __metadata: url-loader: "npm:4.1.1" vaul: "npm:1.1.1" verdaccio: "npm:5.0.4" - viem: "npm:^2.35.0" + viem: "npm:^2.48.0" vite: "npm:~5.0.0" vite-plugin-dts: "npm:3.7.3" vite-plugin-node-polyfills: "npm:0.22.0" @@ -28517,6 +28472,27 @@ __metadata: languageName: node linkType: hard +"ox@npm:0.14.20": + version: 0.14.20 + resolution: "ox@npm:0.14.20" + dependencies: + "@adraffy/ens-normalize": "npm:^1.11.0" + "@noble/ciphers": "npm:^1.3.0" + "@noble/curves": "npm:1.9.1" + "@noble/hashes": "npm:^1.8.0" + "@scure/bip32": "npm:^1.7.0" + "@scure/bip39": "npm:^1.6.0" + abitype: "npm:^1.2.3" + eventemitter3: "npm:5.0.1" + peerDependencies: + typescript: ">=5.4.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/fe1c34577536aea3bda5ec47e083be9069d99cb35a270527921f1c7b406283f12bc4b7e9348b46aa3ba88555fc8abc963129c6b5b8b3b375d5e1549f65f2ce9a + languageName: node + linkType: hard + "ox@npm:0.6.7": version: 0.6.7 resolution: "ox@npm:0.6.7" @@ -28557,27 +28533,6 @@ __metadata: languageName: node linkType: hard -"ox@npm:0.9.6": - version: 0.9.6 - resolution: "ox@npm:0.9.6" - dependencies: - "@adraffy/ens-normalize": "npm:^1.11.0" - "@noble/ciphers": "npm:^1.3.0" - "@noble/curves": "npm:1.9.1" - "@noble/hashes": "npm:^1.8.0" - "@scure/bip32": "npm:^1.7.0" - "@scure/bip39": "npm:^1.6.0" - abitype: "npm:^1.0.9" - eventemitter3: "npm:5.0.1" - peerDependencies: - typescript: ">=5.4.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/559b39051f80a25352e1ca6e7aba6e04f60c4e29f98e4ef3ec0c8d2b0432d400004ce09d2991200eaf21745179af47367dc28c553da43403dd0b69c2453ebabe - languageName: node - linkType: hard - "p-cancelable@npm:^2.0.0": version: 2.1.1 resolution: "p-cancelable@npm:2.1.1" @@ -35080,13 +35035,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.4.0": - version: 2.4.0 - resolution: "tslib@npm:2.4.0" - checksum: 10c0/eb19bda3ae545b03caea6a244b34593468e23d53b26bf8649fbc20fce43e9b21a71127fd6d2b9662c0fe48ee6ff668ead48fd00d3b88b2b716b1c12edae25b5d - languageName: node - linkType: hard - "tslib@npm:2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" @@ -36320,24 +36268,24 @@ __metadata: languageName: node linkType: hard -"viem@npm:^2.35.0": - version: 2.38.6 - resolution: "viem@npm:2.38.6" +"viem@npm:^2.48.0": + version: 2.49.3 + resolution: "viem@npm:2.49.3" dependencies: "@noble/curves": "npm:1.9.1" "@noble/hashes": "npm:1.8.0" "@scure/bip32": "npm:1.7.0" "@scure/bip39": "npm:1.6.0" - abitype: "npm:1.1.0" + abitype: "npm:1.2.3" isows: "npm:1.0.7" - ox: "npm:0.9.6" + ox: "npm:0.14.20" ws: "npm:8.18.3" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/9b8571bc9d7dfc414eb72700275ac71b9402cbf4fe3e447501d252cbc43cb7ddf9ce01a16c183fac265d071873d209efc2a459462c724624ef855d941d3447f0 + checksum: 10c0/f98d0601ec6fecbca0e223735ea59694ac6c6925f85e64b00e46ec57bb1f59c3c5efc84a52abc372875ec730c35254ce6a9cad313dfdcc151ffd6b5d45102a4c languageName: node linkType: hard @@ -37423,21 +37371,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.5.0": - version: 8.5.0 - resolution: "ws@npm:8.5.0" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10c0/0baeee03e97865accda8fad51e8e5fa17d19b8e264529efdf662bbba2acc1c7f1de8316287e6df5cb639231a96009e6d5234b57e6ff36ee2d04e49a0995fec2f - languageName: node - linkType: hard - "ws@npm:^7.0.0, ws@npm:^7.5.1": version: 7.5.10 resolution: "ws@npm:7.5.10"