Skip to content

Commit

Permalink
feat: add minikit provider (#2082)
Browse files Browse the repository at this point in the history
  • Loading branch information
alessey authored Mar 6, 2025
1 parent 4f0404c commit 7c88099
Show file tree
Hide file tree
Showing 11 changed files with 766 additions and 55 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"react-dom": "^18 || ^19"
},
"dependencies": {
"@farcaster/frame-sdk": "^0.0.28",
"@farcaster/frame-wagmi-connector": "^0.0.16",
"@tanstack/react-query": "^5",
"clsx": "^2.1.1",
"graphql": "^14 || ^15 || ^16",
Expand Down
104 changes: 104 additions & 0 deletions src/DefaultOnchainKitProviders.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react';
import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest';
import { http, WagmiProvider, createConfig } from 'wagmi';
import { DefaultOnchainKitProviders } from './DefaultOnchainKitProviders';
import { useProviderDependencies } from './internal/hooks/useProviderDependencies';

const queryClient = new QueryClient();
const wagmiConfig = createConfig({
chains: [
{
id: 1,
name: 'Mock Chain',
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
rpcUrls: { default: { http: ['http://localhost'] } },
},
],
connectors: [],
transports: { [1]: http() },
});

vi.mock('wagmi', async (importOriginal) => {
const actual = await importOriginal<typeof import('wagmi')>();
return {
...actual,
WagmiProvider: vi.fn(({ children }) => (
<div data-testid="wagmi-provider">{children}</div>
)),
};
});

vi.mock('@tanstack/react-query', async (importOriginal) => {
const actual = await importOriginal<typeof import('@tanstack/react-query')>();
return {
...actual,
QueryClientProvider: vi.fn(({ children }) => (
<div data-testid="query-client-provider">{children}</div>
)),
};
});

vi.mock('./internal/hooks/useProviderDependencies', () => ({
useProviderDependencies: vi.fn(() => ({
providedWagmiConfig: vi.fn(),
providedQueryClient: vi.fn(),
})),
}));

describe('DefaultOnchainKitProviders', () => {
beforeEach(() => {
(useProviderDependencies as Mock).mockReturnValue({
providedWagmiConfig: false,
providedQueryClient: false,
});
});

it('should wrap children in default providers', () => {
render(
<DefaultOnchainKitProviders>
<div>Test Child</div>
</DefaultOnchainKitProviders>,
);

expect(screen.getByText('Test Child')).toBeInTheDocument();
expect(screen.queryAllByTestId('wagmi-provider')).toHaveLength(1);
expect(screen.queryAllByTestId('query-client-provider')).toHaveLength(1);
});

it('should not render duplicate default providers when a wagmi provider already exists', () => {
(useProviderDependencies as Mock).mockReturnValue({
providedWagmiConfig: wagmiConfig,
providedQueryClient: null,
});

render(
<WagmiProvider config={wagmiConfig}>
<DefaultOnchainKitProviders>
<div>Test Child</div>
</DefaultOnchainKitProviders>
</WagmiProvider>,
);

expect(screen.getByText('Test Child')).toBeInTheDocument();
expect(screen.queryAllByTestId('wagmi-provider')).toHaveLength(1);
});

it('should not render duplicate default providers when a query client already exists', () => {
(useProviderDependencies as Mock).mockReturnValue({
providedWagmiConfig: null,
providedQueryClient: queryClient,
});

render(
<QueryClientProvider client={queryClient}>
<DefaultOnchainKitProviders>
<div>Test Child</div>
</DefaultOnchainKitProviders>
</QueryClientProvider>,
);

expect(screen.getByText('Test Child')).toBeInTheDocument();
expect(screen.queryAllByTestId('query-client-provider')).toHaveLength(1);
});
});
59 changes: 59 additions & 0 deletions src/DefaultOnchainKitProviders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { type PropsWithChildren, useMemo } from 'react';
import { WagmiProvider } from 'wagmi';
import { coinbaseWallet } from 'wagmi/connectors';
import { createWagmiConfig } from './core/createWagmiConfig';
import type { CreateWagmiConfigParams } from './core/types';
import { useProviderDependencies } from './internal/hooks/useProviderDependencies';

export function DefaultOnchainKitProviders({
apiKey,
appName,
appLogoUrl,
connectors = [
coinbaseWallet({
appName,
appLogoUrl,
preference: 'all',
}),
],
children,
}: PropsWithChildren<CreateWagmiConfigParams>) {
// Check the React context for WagmiProvider and QueryClientProvider
const { providedWagmiConfig, providedQueryClient } =
useProviderDependencies();

const defaultConfig = useMemo(() => {
// IMPORTANT: Don't create a new Wagmi configuration if one already exists
// This prevents the user-provided WagmiConfig from being overridden
return (
providedWagmiConfig ||
createWagmiConfig({
apiKey,
appName,
appLogoUrl,
connectors,
})
);
}, [apiKey, appName, appLogoUrl, connectors, providedWagmiConfig]);

const defaultQueryClient = useMemo(() => {
// IMPORTANT: Don't create a new QueryClient if one already exists
// This prevents the user-provided QueryClient from being overridden
return providedQueryClient || new QueryClient();
}, [providedQueryClient]);

// If both dependencies are missing, return a context with default parent providers
// If only one dependency is provided, expect the user to also provide the missing one
if (!providedWagmiConfig && !providedQueryClient) {
return (
<WagmiProvider config={defaultConfig}>
<QueryClientProvider client={defaultQueryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
);
}

return children;
}
58 changes: 10 additions & 48 deletions src/OnchainKitProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import {
ONCHAIN_KIT_CONFIG,
setOnchainKitConfig,
} from '@/core/OnchainKitConfig';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createContext, useMemo } from 'react';
import { WagmiProvider } from 'wagmi';
import { DefaultOnchainKitProviders } from './DefaultOnchainKitProviders';
import OnchainKitProviderBoundary from './OnchainKitProviderBoundary';
import { DEFAULT_PRIVACY_URL, DEFAULT_TERMS_URL } from './core/constants';
import { createWagmiConfig } from './core/createWagmiConfig';
import type { OnchainKitContextType } from './core/types';
import { COINBASE_VERIFIED_ACCOUNT_SCHEMA_ID } from './identity/constants';
import { useProviderDependencies } from './internal/hooks/useProviderDependencies';
import { checkHashLength } from './internal/utils/checkHashLength';
import type { OnchainKitProviderReact } from './types';

Expand Down Expand Up @@ -85,50 +82,15 @@ export function OnchainKitProvider({
sessionId,
]);

// Check the React context for WagmiProvider and QueryClientProvider
const { providedWagmiConfig, providedQueryClient } =
useProviderDependencies();

const defaultConfig = useMemo(() => {
// IMPORTANT: Don't create a new Wagmi configuration if one already exists
// This prevents the user-provided WagmiConfig from being overridden
return (
providedWagmiConfig ||
createWagmiConfig({
apiKey,
appName: value.config.appearance.name,
appLogoUrl: value.config.appearance.logo,
})
);
}, [
apiKey,
providedWagmiConfig,
value.config.appearance.name,
value.config.appearance.logo,
]);
const defaultQueryClient = useMemo(() => {
// IMPORTANT: Don't create a new QueryClient if one already exists
// This prevents the user-provided QueryClient from being overridden
return providedQueryClient || new QueryClient();
}, [providedQueryClient]);

// If both dependencies are missing, return a context with default parent providers
// If only one dependency is provided, expect the user to also provide the missing one
if (!providedWagmiConfig && !providedQueryClient) {
return (
<WagmiProvider config={defaultConfig}>
<QueryClientProvider client={defaultQueryClient}>
<OnchainKitContext.Provider value={value}>
<OnchainKitProviderBoundary>{children}</OnchainKitProviderBoundary>
</OnchainKitContext.Provider>
</QueryClientProvider>
</WagmiProvider>
);
}

return (
<OnchainKitContext.Provider value={value}>
<OnchainKitProviderBoundary>{children}</OnchainKitProviderBoundary>
</OnchainKitContext.Provider>
<DefaultOnchainKitProviders
apiKey={apiKey}
appName={value.config.appearance.name}
appLogoUrl={value.config.appearance.logo}
>
<OnchainKitContext.Provider value={value}>
<OnchainKitProviderBoundary>{children}</OnchainKitProviderBoundary>
</OnchainKitContext.Provider>
</DefaultOnchainKitProviders>
);
}
15 changes: 8 additions & 7 deletions src/core/createWagmiConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ export const createWagmiConfig = ({
apiKey,
appName,
appLogoUrl,
connectors = [
coinbaseWallet({
appName,
appLogoUrl,
preference: 'all',
}),
],
}: CreateWagmiConfigParams) => {
return createConfig({
chains: [base, baseSepolia],
connectors: [
coinbaseWallet({
appName,
appLogoUrl,
preference: 'all',
}),
],
connectors,
storage: createStorage({
storage: cookieStorage,
}),
Expand Down
3 changes: 3 additions & 0 deletions src/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { EASSchemaUid } from '@/identity/types';
import type { Address, Chain } from 'viem';
import type { CreateConnectorFn } from 'wagmi';

/**
* Note: exported as public Type
Expand Down Expand Up @@ -38,6 +39,8 @@ export type CreateWagmiConfigParams = {
appName?: string;
/** Application logo URL */
appLogoUrl?: string;
/** Connectors to use, defaults to coinbaseWallet */
connectors?: CreateConnectorFn[];
};

/**
Expand Down
Loading

0 comments on commit 7c88099

Please sign in to comment.