Skip to content

There are two layers, and most apps use both:

  • The web components. Drop <frak-button-share>, <frak-banner>, and <frak-post-purchase> straight into your JSX. No provider, no hooks. Start here.
  • @frak-labs/react-sdk. Providers and hooks for custom flows: wallet status, modals, referrals, SSO, transactions.
  1. A React 18 or 19 project.
  2. A merchant account on the Frak business dashboard with your domain registered. The main domain is registered at sign-up; add subdomains under Allowed Domains.

If you have not done that yet, start with the Get started guide.

Install @frak-labs/components, set the config in its own module, and import the components you use before rendering. The package manager guide covers that setup, and Add Frak to a custom website shows a full React example including the TypeScript declaration for the <frak-*> tags.

@tanstack/react-query and viem are peer dependencies: the hooks are React Query queries and mutations, and the SDK ships no QueryClient of its own.

Terminal window
npm install @frak-labs/react-sdk @tanstack/react-query viem

Three providers, in this order: your QueryClientProvider, then FrakConfigProvider (holds the config), then FrakIFrameClientProvider (creates the listener iframe and the client).

FrakProvider.tsx
import {
import FrakConfigProvider
FrakConfigProvider
,
import FrakIFrameClientProvider
FrakIFrameClientProvider
,
} from "@frak-labs/react-sdk";
import {
class QueryClient
QueryClient
,
const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.Element
QueryClientProvider
} from "@tanstack/react-query";
import type {
type PropsWithChildren<P = unknown> = P & {
children?: React.ReactNode | undefined;
}
PropsWithChildren
} from "react";
const
const queryClient: QueryClient
queryClient
= new
new QueryClient(config?: QueryClientConfig): QueryClient
QueryClient
();
const
const frakConfig: {
metadata: {
name: string;
};
}
frakConfig
= {
metadata: {
name: string;
}
metadata
: {
name: string
name
: "Your App Name",
},
// The domain will be automatically set to window.location.host
};
export function
function FrakProvider({ children }: PropsWithChildren): React.JSX.Element
FrakProvider
({
children: React.ReactNode
children
}:
type PropsWithChildren<P = unknown> = P & {
children?: React.ReactNode | undefined;
}
PropsWithChildren
) {
return (
<
const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.Element
QueryClientProvider
client: QueryClient
client
={
const queryClient: QueryClient
queryClient
}>
<
import FrakConfigProvider
FrakConfigProvider
config: {
metadata: {
name: string;
};
}
config
={
const frakConfig: {
metadata: {
name: string;
};
}
frakConfig
}>
<
import FrakIFrameClientProvider
FrakIFrameClientProvider
>{
children: React.ReactNode
children
}</
import FrakIFrameClientProvider
FrakIFrameClientProvider
>
</
import FrakConfigProvider
FrakConfigProvider
>
</
const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.Element
QueryClientProvider
>
);
}

Wrap your app with it:

App.tsx
import {
import FrakProvider
FrakProvider
} from './FrakProvider';
function
function App(): any
App
() {
return (
<
import FrakProvider
FrakProvider
>
{/* Your app content */}
</
import FrakProvider
FrakProvider
>
);
}
export default
function App(): any
App
;

The config takes the same fields as everywhere else. See FrakSetup and Configuration.

WalletStatus.tsx
/**
* Simple wallet status component
*/
function
function WalletStatus(): any

Simple wallet status component

WalletStatus
() {
const {
any
data
:
const walletStatus: any
walletStatus
,
const isLoading: any
isLoading
,
const error: any
error
} =
any
useWalletStatus
();
if (
const isLoading: any
isLoading
) return <
any
div
>Loading wallet status...</
any
div
>;
if (
const error: any
error
) return <
any
div
>Error: {
const error: any
error
.
any
message
}</
any
div
>;
return (
<
any
div
>
Wallet status:{" "}
{
const walletStatus: any
walletStatus
?.
any
key
=== "connected" ? "Connected" : "Not connected"}
</
any
div
>
);
}
LoginButton.tsx
/**
* Simple login with frak button
*/
function
function LoginButton(): any

Simple login with frak button

LoginButton
() {
const {
any
mutate
:
const displayModal: any
displayModal
,
const isPending: any
isPending
} =
any
useDisplayModal
();
const
const handleLogin: () => void
handleLogin
= () => {
const displayModal: any
displayModal
({
steps: {
login: {};
}
steps
: {
login: {}
login
: {},
},
metadata: {
i18n: {
"sdk.modal.login.description": string;
};
}
metadata
: {
i18n: {
"sdk.modal.login.description": string;
}
i18n
: {
"sdk.modal.login.description":
"Login with Frak to receive up to {{ estimatedReward }}!",
},
},
});
};
return (
<
any
button
onClick: () => void
onClick
={
const handleLogin: () => void
handleLogin
}
disabled: any
disabled
={
const isPending: any
isPending
}
type: string
type
={"button"}>
{
const isPending: any
isPending
? "Logging in..." : "Login with Frak"}
</
any
button
>
);
}
function
function App(): any
App
() {
return (
<
any
FrakProvider
>
<
any
h1
>My Frak Powered app</
any
h1
>
<
any
WalletStatus
/>
<
any
LoginButton
/>
</
any
FrakProvider
>
);
}
export default
function App(): any
App
;
HookReturnsWhat it does
useWalletStatus()queryCurrent wallet status, kept in sync
useDisplayModal()mutationOpen a modal from steps, with an optional placement
useDisplaySharingPage()mutationOpen the sharing page
useReferralInteraction()valueProcesses an inbound referral once the client is ready, returns the state or an error
useSetupReferral()side effectWires referral handling, emits frak:referral-success
useGetMerchantInformation()queryYour merchant data as resolved by the backend
useGetUserReferralStatus()queryThe user’s referral state
useGetMergeToken()queryToken used to merge a wallet
useSiweAuthenticate()mutationSign-In with Ethereum
useSendTransactionAction()mutationAsk the wallet to send a transaction
useOpenSso()mutationOpen the SSO flow
usePrepareSso(params)queryPrepare an SSO session
usePrepareSsoUrl(params)queryBuild the SSO URL ahead of time, so the open is a direct user gesture and dodges popup blockers
useFrakClient()valueThe raw client, or undefined before it is ready
useFrakConfig()valueThe resolved config. Throws outside a FrakConfigProvider

Most mutation hooks take an optional { mutations } object of React Query options, and most query hooks take { query }. Two exceptions: useWalletStatus() takes no argument, and usePrepareSso(params) takes the SSO parameters directly.

Full signatures live in the generated SDK reference.

One hook is enough on the page a referred visitor lands on:

import { useReferralInteraction } from "@frak-labs/react-sdk";
export function ReferralHandler() {
const state = useReferralInteraction();
// "idle" | "processing" | a referral state | an Error
return null;
}

Purchase tracking has no hook: call the action, which needs no client.

import { trackPurchaseStatus } from "@frak-labs/core-sdk/actions";
await trackPurchaseStatus({
customerId: "cust_123",
orderId: "order_456",
token: "a-unique-order-token",
});

Rewards are only released once your backend confirms the order with a signed webhook. See Validate purchases from your backend.