diff --git a/.changeset/plain-ants-begin.md b/.changeset/plain-ants-begin.md new file mode 100644 index 0000000000..095d12d523 --- /dev/null +++ b/.changeset/plain-ants-begin.md @@ -0,0 +1,27 @@ +--- +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit-utils': patch +'@reown/appkit-controllers': patch +'@reown/appkit': patch +'@reown/appkit-wallet': patch +'@reown/appkit-adapter-bitcoin': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-codemod': patch +'@reown/appkit-common': patch +'@reown/appkit-core': patch +'@reown/appkit-experimental': patch +'@reown/appkit-pay': patch +'@reown/appkit-polyfills': patch +'@reown/appkit-scaffold-ui': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-testing': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet-button': patch +--- + +Add forwarding of custom RPC urls to be used in embedded wallet requests diff --git a/apps/laboratory/app/flag/custom-rpc-url/page.tsx b/apps/laboratory/app/flag/custom-rpc-url/page.tsx new file mode 100644 index 0000000000..329695cefa --- /dev/null +++ b/apps/laboratory/app/flag/custom-rpc-url/page.tsx @@ -0,0 +1,50 @@ +'use client' + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { WagmiProvider } from 'wagmi' + +import { WagmiAdapter } from '@reown/appkit-adapter-wagmi' + +import { AppKitButtons } from '@/src/components/AppKitButtons' +import { AppKitConnections } from '@/src/components/AppKitConnections' +import { AppKitInfo } from '@/src/components/AppKitInfo' +import { WagmiTests } from '@/src/components/Wagmi/WagmiTests' +import { AppKitProvider } from '@/src/context/AppKitContext' +import { ConstantsUtil } from '@/src/utils/ConstantsUtil' + +const queryClient = new QueryClient() + +const customRpcUrls = { + 'eip155:1': [{ url: 'https://ethereum-rpc.publicnode.com' }] +} + +const wagmiAdapter = new WagmiAdapter({ + ssr: true, + networks: ConstantsUtil.EvmNetworks, + projectId: ConstantsUtil.ProjectId, + customRpcUrls +}) + +const config = { + adapters: [wagmiAdapter], + networks: ConstantsUtil.EvmNetworks, + projectId: ConstantsUtil.ProjectId, + customWallets: ConstantsUtil.CustomWallets, + customRpcUrls +} +const wagmiConfig = wagmiAdapter.wagmiConfig + +export default function Wagmi() { + return ( + + + + + + + + + + + ) +} diff --git a/packages/adapters/ethers/src/tests/client.test.ts b/packages/adapters/ethers/src/tests/client.test.ts index 847174610d..97c764a868 100644 --- a/packages/adapters/ethers/src/tests/client.test.ts +++ b/packages/adapters/ethers/src/tests/client.test.ts @@ -964,7 +964,7 @@ describe('EthersAdapter', () => { providerType: 'AUTH' }) - expect(mockAuthProvider.switchNetwork).toHaveBeenCalledWith('eip155:1') + expect(mockAuthProvider.switchNetwork).toHaveBeenCalledWith({ chainId: 'eip155:1' }) expect(mockAuthProvider.getUser).toHaveBeenCalledWith({ chainId: 'eip155:1', preferredAccountType: 'smartAccount' diff --git a/packages/adapters/ethers5/src/tests/client.test.ts b/packages/adapters/ethers5/src/tests/client.test.ts index 01fc4dcac5..7af627849e 100644 --- a/packages/adapters/ethers5/src/tests/client.test.ts +++ b/packages/adapters/ethers5/src/tests/client.test.ts @@ -862,7 +862,7 @@ describe('Ethers5Adapter', () => { providerType: 'AUTH' }) - expect(mockAuthProvider.switchNetwork).toHaveBeenCalledWith('eip155:1') + expect(mockAuthProvider.switchNetwork).toHaveBeenCalledWith({ chainId: 'eip155:1' }) expect(mockAuthProvider.getUser).toHaveBeenCalledWith({ chainId: 'eip155:1', preferredAccountType: 'smartAccount' diff --git a/packages/adapters/solana/src/tests/mocks/W3mFrameProvider.ts b/packages/adapters/solana/src/tests/mocks/W3mFrameProvider.ts index 52a0ce903e..93245447f7 100644 --- a/packages/adapters/solana/src/tests/mocks/W3mFrameProvider.ts +++ b/packages/adapters/solana/src/tests/mocks/W3mFrameProvider.ts @@ -46,7 +46,9 @@ export function mockW3mFrameProvider() { return Promise.reject(new Error('not implemented')) } }) - w3mFrame.switchNetwork = vi.fn((chainId: string | number) => Promise.resolve({ chainId })) + w3mFrame.switchNetwork = vi.fn((args: { chainId: string | number }) => + Promise.resolve({ chainId: args.chainId }) + ) w3mFrame.getUser = vi.fn(() => Promise.resolve(mockSession())) w3mFrame.user = mockSession() diff --git a/packages/adapters/wagmi/src/connectors/AuthConnector.ts b/packages/adapters/wagmi/src/connectors/AuthConnector.ts index a44c21c4a1..55c7724a62 100644 --- a/packages/adapters/wagmi/src/connectors/AuthConnector.ts +++ b/packages/adapters/wagmi/src/connectors/AuthConnector.ts @@ -138,6 +138,7 @@ export function authConnector(parameters: AuthParameters) { chainId?: number isReconnecting?: boolean socialUri?: string + rpcUrl?: string } = {} ) { if (connectSocialPromise) { diff --git a/packages/adapters/wagmi/src/tests/client.test.ts b/packages/adapters/wagmi/src/tests/client.test.ts index dd51ba7885..7ea1725367 100644 --- a/packages/adapters/wagmi/src/tests/client.test.ts +++ b/packages/adapters/wagmi/src/tests/client.test.ts @@ -877,7 +877,7 @@ describe('WagmiAdapter', () => { chainId: 'eip155:1', preferredAccountType: 'smartAccount' }) - expect(mockAuthProvider.switchNetwork).toHaveBeenCalledWith('eip155:1') + expect(mockAuthProvider.switchNetwork).toHaveBeenCalledWith({ chainId: 'eip155:1' }) }) }) diff --git a/packages/appkit/src/adapters/ChainAdapterBlueprint.ts b/packages/appkit/src/adapters/ChainAdapterBlueprint.ts index ce2c71fd48..a0472fd7b7 100644 --- a/packages/appkit/src/adapters/ChainAdapterBlueprint.ts +++ b/packages/appkit/src/adapters/ChainAdapterBlueprint.ts @@ -351,7 +351,7 @@ export abstract class AdapterBlueprint< if (provider && providerType === 'AUTH') { const authProvider = provider as W3mFrameProvider const preferredAccountType = getPreferredAccountType(caipNetwork.chainNamespace) - await authProvider.switchNetwork(caipNetwork.caipNetworkId) + await authProvider.switchNetwork({ chainId: caipNetwork.caipNetworkId }) const user = await authProvider.getUser({ chainId: caipNetwork.caipNetworkId, preferredAccountType diff --git a/packages/controllers/src/utils/ConnectorControllerUtil.ts b/packages/controllers/src/utils/ConnectorControllerUtil.ts index 35f8cc0a50..109c200797 100644 --- a/packages/controllers/src/utils/ConnectorControllerUtil.ts +++ b/packages/controllers/src/utils/ConnectorControllerUtil.ts @@ -150,7 +150,11 @@ export const ConnectorControllerUtil = { properties: { provider: socialProvider } }) } - await authConnector.provider.connectSocial(uri) + const network = ChainController.state.activeCaipNetwork + await authConnector.provider.connectSocial({ + uri, + chainId: network?.caipNetworkId + }) if (socialProvider) { StorageUtil.setConnectedSocialProvider(socialProvider) diff --git a/packages/siwx/src/configs/DefaultSIWX.ts b/packages/siwx/src/configs/DefaultSIWX.ts index cd5d59d135..940e789306 100644 --- a/packages/siwx/src/configs/DefaultSIWX.ts +++ b/packages/siwx/src/configs/DefaultSIWX.ts @@ -9,7 +9,12 @@ const DEFAULTS = { new InformalMessenger({ domain: typeof document === 'undefined' ? 'Unknown Domain' : document.location.host, uri: typeof document === 'undefined' ? 'Unknown URI' : document.location.href, - getNonce: async () => Promise.resolve(Math.round(Math.random() * 10000).toString()) + getNonce: async () => + Promise.resolve( + Math.round(Math.random() * 100000000) + .toString() + .padStart(8, '0') + ) }), getDefaultVerifiers: () => [new EIP155Verifier(), new SolanaVerifier(), new BIP122Verifier()], diff --git a/packages/wallet/src/W3mFrame.ts b/packages/wallet/src/W3mFrame.ts index f635ae9ff7..ea108a79ac 100644 --- a/packages/wallet/src/W3mFrame.ts +++ b/packages/wallet/src/W3mFrame.ts @@ -16,6 +16,30 @@ interface W3mFrameConfig { isAppClient?: boolean chainId?: W3mFrameTypes.Network['chainId'] enableLogger?: boolean + rpcUrl?: string +} + +function createSecureSiteSdkUrl({ + projectId, + chainId, + version, + enableLogger, + rpcUrl = ConstantsUtil.BLOCKCHAIN_API_RPC_URL +}: { + projectId: string + chainId: W3mFrameTypes.Network['chainId'] + version: string + enableLogger: boolean + rpcUrl?: string +}): string { + const url = new URL(SECURE_SITE_SDK) + url.searchParams.set('projectId', projectId) + url.searchParams.set('chainId', String(chainId)) + url.searchParams.set('version', version) + url.searchParams.set('enableLogger', String(enableLogger)) + url.searchParams.set('rpcUrl', rpcUrl) + + return url.toString() } // -- Sdk -------------------------------------------------------------------- @@ -26,7 +50,7 @@ export class W3mFrame { private projectId: string - private rpcUrl = ConstantsUtil.BLOCKCHAIN_API_RPC_URL + private rpcUrl: string public frameLoadPromise: Promise @@ -41,13 +65,15 @@ export class W3mFrame { projectId, isAppClient = false, chainId = 'eip155:1', - enableLogger = true + enableLogger = true, + rpcUrl = ConstantsUtil.BLOCKCHAIN_API_RPC_URL }: W3mFrameConfig) { this.projectId = projectId this.frameLoadPromise = new Promise((resolve, reject) => { this.frameLoadPromiseResolver = { resolve, reject } }) + this.rpcUrl = rpcUrl // Create iframe only when sdk is initialised from dapp / appkit if (isAppClient) { this.frameLoadPromise = new Promise((resolve, reject) => { @@ -56,7 +82,13 @@ export class W3mFrame { if (W3mFrameHelpers.isClient) { const iframe = document.createElement('iframe') iframe.id = 'w3m-iframe' - iframe.src = `${SECURE_SITE_SDK}?projectId=${projectId}&chainId=${chainId}&version=${SECURE_SITE_SDK_VERSION}&enableLogger=${enableLogger}` + iframe.src = createSecureSiteSdkUrl({ + projectId, + chainId, + version: SECURE_SITE_SDK_VERSION, + enableLogger, + rpcUrl: this.rpcUrl + }) iframe.name = 'w3m-secure-iframe' iframe.style.position = 'fixed' iframe.style.zIndex = '999999' diff --git a/packages/wallet/src/W3mFrameProvider.ts b/packages/wallet/src/W3mFrameProvider.ts index e0d3a5c0a9..b98cbc67d3 100644 --- a/packages/wallet/src/W3mFrameProvider.ts +++ b/packages/wallet/src/W3mFrameProvider.ts @@ -1,5 +1,9 @@ -import type { ChainNamespace, EmbeddedWalletTimeoutReason } from '@reown/appkit-common' -import type { CaipNetwork } from '@reown/appkit-common' +import { + type ChainNamespace, + type EmbeddedWalletTimeoutReason, + ParseUtil +} from '@reown/appkit-common' +import type { CaipNetwork, CaipNetworkId } from '@reown/appkit-common' import { W3mFrame } from './W3mFrame.js' import { W3mFrameConstants, W3mFrameRpcConstants } from './W3mFrameConstants.js' @@ -53,8 +57,8 @@ export class W3mFrameProvider { } this.abortController = abortController this.getActiveCaipNetwork = getActiveCaipNetwork - - this.w3mFrame = new W3mFrame({ projectId, isAppClient: true, chainId, enableLogger }) + const rpcUrl = this.getRpcUrl(chainId) + this.w3mFrame = new W3mFrame({ projectId, isAppClient: true, chainId, enableLogger, rpcUrl }) this.onTimeout = onTimeout if (this.getLoginEmailUsed()) { this.createFrame() @@ -315,13 +319,15 @@ export class W3mFrameProvider { if (payload?.socialUri) { try { await this.init() + const rpcUrl = this.getRpcUrl(payload.chainId) const response = await this.appEvent<'ConnectSocial'>({ type: W3mFrameConstants.APP_CONNECT_SOCIAL, payload: { uri: payload.socialUri, preferredAccountType: payload.preferredAccountType, chainId: payload.chainId, - siwxMessage: payload.siwxMessage + siwxMessage: payload.siwxMessage, + rpcUrl } } as W3mFrameTypes.AppEvent) @@ -346,7 +352,8 @@ export class W3mFrameProvider { const response = await this.getUser({ chainId, preferredAccountType: payload?.preferredAccountType, - siwxMessage: payload?.siwxMessage + siwxMessage: payload?.siwxMessage, + rpcUrl: this.getRpcUrl(chainId) }) this.setLoginSuccess(response.email) @@ -378,12 +385,21 @@ export class W3mFrameProvider { } } - public async connectSocial(uri: string) { + public async connectSocial({ + uri, + chainId, + preferredAccountType + }: { + uri: string + chainId?: number | string + preferredAccountType?: string + }) { try { await this.init() + const rpcUrl = this.getRpcUrl(chainId) const response = await this.appEvent<'ConnectSocial'>({ type: W3mFrameConstants.APP_CONNECT_SOCIAL, - payload: { uri } + payload: { uri, chainId, rpcUrl, preferredAccountType } } as W3mFrameTypes.AppEvent) if (response.userName) { @@ -428,11 +444,12 @@ export class W3mFrameProvider { } } - public async switchNetwork(chainId: number | string) { + public async switchNetwork({ chainId }: { chainId: number | string }) { try { + const rpcUrl = this.getRpcUrl(chainId) const response = await this.appEvent<'SwitchNetwork'>({ type: W3mFrameConstants.APP_SWITCH_NETWORK, - payload: { chainId } + payload: { chainId, rpcUrl } } as W3mFrameTypes.AppEvent) this.setLastUsedChainId(response.chainId) @@ -478,8 +495,8 @@ export class W3mFrameProvider { const namespace = req.chainNamespace || 'eip155' const chainId = this.getActiveCaipNetwork(namespace)?.id request.chainNamespace = namespace - request.chainId = chainId + request.rpcUrl = this.getRpcUrl(chainId) this.rpcRequestHandler?.(req) const response = await this.appEvent<'Rpc'>({ @@ -750,6 +767,24 @@ export class W3mFrameProvider { private persistSmartAccountEnabledNetworks(networks: number[]) { W3mFrameStorage.set(W3mFrameConstants.SMART_ACCOUNT_ENABLED_NETWORKS, networks.join(',')) } + + private getRpcUrl(chainId?: number | string) { + let namespace: ChainNamespace | undefined = chainId === undefined ? undefined : 'eip155' + + if (typeof chainId === 'string') { + if (chainId.includes(':')) { + namespace = ParseUtil.parseCaipNetworkId(chainId as CaipNetworkId)?.chainNamespace + } else if (Number.isInteger(Number(chainId))) { + namespace = 'eip155' + } else { + namespace = 'solana' + } + } + + const activeNetwork = this.getActiveCaipNetwork(namespace) + + return activeNetwork?.rpcUrls.default.http?.[0] + } } export interface W3mFrameProviderMethods { diff --git a/packages/wallet/src/W3mFrameSchema.ts b/packages/wallet/src/W3mFrameSchema.ts index 2222111332..e36f081aac 100644 --- a/packages/wallet/src/W3mFrameSchema.ts +++ b/packages/wallet/src/W3mFrameSchema.ts @@ -52,20 +52,25 @@ export const GetTransactionByHashResponse = z.object({ v: z.string(), value: z.string() }) -export const AppSwitchNetworkRequest = z.object({ chainId: z.string().or(z.number()) }) +export const AppSwitchNetworkRequest = z.object({ + chainId: z.string().or(z.number()), + rpcUrl: z.optional(z.string()) +}) export const AppConnectEmailRequest = z.object({ email: z.string().email() }) export const AppConnectOtpRequest = z.object({ otp: z.string() }) export const AppConnectSocialRequest = z.object({ uri: z.string(), preferredAccountType: z.optional(z.string()), chainId: z.optional(z.string().or(z.number())), - siwxMessage: z.optional(SIWXMessage) + siwxMessage: z.optional(SIWXMessage), + rpcUrl: z.optional(z.string()) }) export const AppGetUserRequest = z.object({ chainId: z.optional(z.string().or(z.number())), preferredAccountType: z.optional(z.string()), socialUri: z.optional(z.string()), - siwxMessage: z.optional(SIWXMessage) + siwxMessage: z.optional(SIWXMessage), + rpcUrl: z.optional(z.string()) }) export const AppGetSocialRedirectUriRequest = z.object({ provider: z.enum(['google', 'github', 'apple', 'facebook', 'x', 'discord']) @@ -568,7 +573,8 @@ export const W3mFrameSchema = { chainId: z.string().or(z.number()).optional(), chainNamespace: z .enum(['eip155', 'solana', 'polkadot', 'bip122', 'cosmos']) - .optional() + .optional(), + rpcUrl: z.string().optional() }) ) }) diff --git a/packages/wallet/src/W3mFrameTypes.ts b/packages/wallet/src/W3mFrameTypes.ts index 24b1b00b36..6f94127359 100644 --- a/packages/wallet/src/W3mFrameTypes.ts +++ b/packages/wallet/src/W3mFrameTypes.ts @@ -192,7 +192,7 @@ export namespace W3mFrameTypes { | z.infer | z.infer | z.infer - ) & { chainNamespace?: ChainNamespace; chainId?: string | number } + ) & { chainNamespace?: ChainNamespace; chainId?: string | number; rpcUrl?: string } export type RPCResponse = z.infer diff --git a/packages/wallet/tests/W3mFrame.test.ts b/packages/wallet/tests/W3mFrame.test.ts index 01b1f998a9..30f03e7fc1 100644 --- a/packages/wallet/tests/W3mFrame.test.ts +++ b/packages/wallet/tests/W3mFrame.test.ts @@ -1,5 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { ConstantsUtil } from '@reown/appkit-common' + import { W3mFrame } from '../src/W3mFrame.js' import { W3mFrameConstants } from '../src/W3mFrameConstants.js' import { W3mFrameHelpers } from '../src/W3mFrameHelpers.js' @@ -199,4 +201,32 @@ describe('W3mFrame', () => { ) }) }) + + describe('rpcUrl', () => { + it('should default to ConstantsUtil.BLOCKCHAIN_API_RPC_URL when rpcUrl is undefined', () => { + w3mFrame = new W3mFrame({ projectId: PROJECT_ID, isAppClient: true }) + + const encodedDefaultRpcUrl = encodeURIComponent(ConstantsUtil.BLOCKCHAIN_API_RPC_URL) + + // iframe src should contain the encoded default rpc url + expect(w3mFrame['iframe']?.src).toContain(`rpcUrl=${encodedDefaultRpcUrl}`) + + // networks getter should construct rpc urls based on the default value + const expectedNetworkRpcUrl = `${ConstantsUtil.BLOCKCHAIN_API_RPC_URL}/v1/?chainId=eip155:1&projectId=${PROJECT_ID}` + expect(w3mFrame.networks?.['eip155:1']?.rpcUrl).toBe(expectedNetworkRpcUrl) + }) + + it('should use the provided custom rpcUrl when supplied', () => { + const CUSTOM_RPC_URL = 'https://custom.rpc.domain' + + w3mFrame = new W3mFrame({ projectId: PROJECT_ID, isAppClient: true, rpcUrl: CUSTOM_RPC_URL }) + + // iframe src should contain the encoded custom rpc url + expect(w3mFrame['iframe']?.src).toContain(`rpcUrl=${encodeURIComponent(CUSTOM_RPC_URL)}`) + + // networks getter should construct rpc urls based on the custom value + const expectedNetworkRpcUrl = `${CUSTOM_RPC_URL}/v1/?chainId=eip155:1&projectId=${PROJECT_ID}` + expect(w3mFrame.networks['eip155:1']?.rpcUrl).toBe(expectedNetworkRpcUrl) + }) + }) }) diff --git a/packages/wallet/tests/W3mFrameProvider.test.ts b/packages/wallet/tests/W3mFrameProvider.test.ts index 20be4a279a..06831c5be3 100644 --- a/packages/wallet/tests/W3mFrameProvider.test.ts +++ b/packages/wallet/tests/W3mFrameProvider.test.ts @@ -1,7 +1,11 @@ import * as logger from '@walletconnect/logger' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { EmbeddedWalletTimeoutReason } from '@reown/appkit-common' +import type { + CaipNetworkId, + ChainNamespace, + EmbeddedWalletTimeoutReason +} from '@reown/appkit-common' import { W3mFrameConstants } from '../src/W3mFrameConstants.js' import { W3mFrameProvider } from '../src/W3mFrameProvider.js' @@ -10,6 +14,15 @@ import { SecureSiteMock } from './mocks/SecureSite.mock.js' // Mocks import { W3mFrameHelpers } from './mocks/W3mFrameHelpers.mock.js' +const getActiveCaipNetwork = () => ({ + name: 'Ethereum', + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { default: { http: ['https://rpc.ankr.com/eth'] } }, + id: 'eip155:1', + chainNamespace: 'eip155' as ChainNamespace, + caipNetworkId: 'eip155:1' as CaipNetworkId +}) + describe('W3mFrameProvider', () => { const mockTimeout = vi.fn<(reason: EmbeddedWalletTimeoutReason) => void>() @@ -19,14 +32,19 @@ describe('W3mFrameProvider', () => { beforeEach(() => { abortController = new AbortController() - provider = new W3mFrameProvider({ projectId, abortController, onTimeout: mockTimeout }) + provider = new W3mFrameProvider({ + projectId, + abortController, + onTimeout: mockTimeout, + getActiveCaipNetwork + }) window.postMessage = vi.fn() mockTimeout.mockClear() }) it('should connect email', async () => { provider['w3mFrame'].frameLoadPromise = Promise.resolve() - provider.isInitialized = true + provider['isInitialized'] = true const payload = { email: 'test@example.com' } W3mFrameHelpers.checkIfAllowedToTriggerEmail.mockReturnValue(true) const responsePayload = { action: 'VERIFY_OTP' } @@ -53,7 +71,7 @@ describe('W3mFrameProvider', () => { it('should connect otp', async () => { provider['w3mFrame'].frameLoadPromise = Promise.resolve() - provider.isInitialized = true + provider['isInitialized'] = true const payload = { otp: '123456' } const postAppEventSpy = vi .spyOn(provider['w3mFrame'].events, 'postAppEvent') @@ -73,7 +91,7 @@ describe('W3mFrameProvider', () => { it('should connect', async () => { provider['w3mFrame'].frameLoadPromise = Promise.resolve() - provider.isInitialized = true + provider['isInitialized'] = true const payload = { chainId: 1 } const responsePayload = { address: '0xd34db33f', chainId: 1, email: 'test@walletconnect.com' } @@ -95,7 +113,7 @@ describe('W3mFrameProvider', () => { it('should connect social', async () => { provider['w3mFrame'].frameLoadPromise = Promise.resolve() - provider.isInitialized = true + provider['isInitialized'] = true const payload = { chainId: 1, socialUri: '?auth=12345678' } const responsePayload = { address: '0xd34db33f', @@ -141,7 +159,7 @@ describe('W3mFrameProvider', () => { }) }) - const response = await provider.switchNetwork(chainId) + const response = await provider.switchNetwork({ chainId }) expect(response).toEqual(responsePayload) expect(postAppEventSpy).toHaveBeenCalled() @@ -155,12 +173,13 @@ describe('W3mFrameProvider', () => { const testProvider = new W3mFrameProvider({ projectId, onTimeout: onTimeoutMock, - abortController: testAbortController + abortController: testAbortController, + getActiveCaipNetwork }) vi.spyOn(testProvider['w3mFrame'].events, 'postAppEvent').mockImplementation(() => {}) - testProvider.isInitialized = true + testProvider['isInitialized'] = true testProvider['w3mFrame'].iframeIsReady = true testProvider['w3mFrame'].frameLoadPromise = Promise.resolve() @@ -181,7 +200,12 @@ describe('W3mFrameProvider', () => { it('should create logger if enableLogger is undefined', async () => { const generateChildLoggerSpy = vi.spyOn(logger, 'generateChildLogger') - provider = new W3mFrameProvider({ projectId, enableLogger: true, abortController }) + provider = new W3mFrameProvider({ + projectId, + enableLogger: true, + abortController, + getActiveCaipNetwork + }) provider['w3mFrame'].frameLoadPromise = Promise.resolve() expect(generateChildLoggerSpy).toHaveBeenCalled() }) @@ -189,7 +213,12 @@ describe('W3mFrameProvider', () => { it('should create logger if enableLogger is true', async () => { const generatePlatformLoggerSpy = vi.spyOn(logger, 'generatePlatformLogger') - provider = new W3mFrameProvider({ projectId, enableLogger: true, abortController }) + provider = new W3mFrameProvider({ + projectId, + enableLogger: true, + abortController, + getActiveCaipNetwork + }) provider['w3mFrame'].frameLoadPromise = Promise.resolve() expect(generatePlatformLoggerSpy).toHaveBeenCalled() }) @@ -197,7 +226,12 @@ describe('W3mFrameProvider', () => { it('should not create logger if enableLogger is false', async () => { const generatePlatformLoggerSpy = vi.spyOn(logger, 'generatePlatformLogger') - provider = new W3mFrameProvider({ projectId, enableLogger: false, abortController }) + provider = new W3mFrameProvider({ + projectId, + enableLogger: false, + abortController, + getActiveCaipNetwork + }) provider['w3mFrame'].frameLoadPromise = Promise.resolve() expect(generatePlatformLoggerSpy).not.toHaveBeenCalled() }) @@ -210,10 +244,11 @@ describe('W3mFrameProvider', () => { const testProvider = new W3mFrameProvider({ projectId, onTimeout: onTimeoutMock, - abortController: testAbortController + abortController: testAbortController, + getActiveCaipNetwork }) - testProvider.isInitialized = true + testProvider['isInitialized'] = true testProvider['w3mFrame'].frameLoadPromise = Promise.resolve() testProvider.connectEmail({ email: 'test@example.com' }).catch(() => {}) @@ -229,4 +264,44 @@ describe('W3mFrameProvider', () => { vi.useRealTimers() }) + + it('should return correct rpc url based on chainId parameter in getRpcUrl', () => { + const eip155RpcUrl = 'https://rpc.ankr.com/eth' + const solanaRpcUrl = 'https://api.mainnet-beta.solana.com' + + const customGetActiveCaipNetwork = (namespace?: ChainNamespace) => { + if (namespace === 'solana') { + return { + name: 'Solana', + nativeCurrency: { name: 'Sol', symbol: 'SOL', decimals: 9 }, + rpcUrls: { default: { http: [solanaRpcUrl] } }, + id: 'solana:1', + chainNamespace: 'solana' as ChainNamespace, + caipNetworkId: 'solana:1' as CaipNetworkId + } + } + + return { + name: 'Ethereum', + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { default: { http: [eip155RpcUrl] } }, + id: 'eip155:1', + chainNamespace: 'eip155' as ChainNamespace, + caipNetworkId: 'eip155:1' as CaipNetworkId + } + } + + const rpcProvider = new W3mFrameProvider({ + projectId, + enableLogger: false, + abortController: new AbortController(), + getActiveCaipNetwork: customGetActiveCaipNetwork + }) + + expect(rpcProvider['getRpcUrl']()).toBe(eip155RpcUrl) + expect(rpcProvider['getRpcUrl'](1)).toBe(eip155RpcUrl) + expect(rpcProvider['getRpcUrl']('eip155:1')).toBe(eip155RpcUrl) + expect(rpcProvider['getRpcUrl']('1')).toBe(eip155RpcUrl) + expect(rpcProvider['getRpcUrl']('AzDjsjkalwnalsdnj2kh')).toBe(solanaRpcUrl) + }) })