> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.clossir.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.clossir.com/_mcp/server.

# React Integration

GETTING STARTED · SDK · REACT INTEGRATION

`@signum-tech/sdk-react` is a companion package that wraps the Clossir SDK with
[TanStack Query](https://tanstack.com/query) hooks and a wallet management hook for
React applications.

## Installation

```bash title="Install"
npm install @signum-tech/sdk @signum-tech/sdk-react @tanstack/react-query
```

Peer dependencies: React 18+, `@tanstack/react-query` 5+, `@signum-tech/sdk` 0.3.1+.

## SignumProvider

Wrap your app with `SignumProvider` to make the SDK client available to all hooks:

```tsx title="app.tsx"
import { SignumProvider } from "@signum-tech/sdk-react";

function App() {
  return (
    <SignumProvider
      options={{
        serverURL: process.env.NEXT_PUBLIC_SIGNUM_API_URL,
        bearerAuth: getAccessToken,
      }}
    >
      <YourApp />
    </SignumProvider>
  );
}
```

`SignumProvider` creates a client instance (memoized on `options`) and wraps your
app in a TanStack `QueryClientProvider`. If you already have a `QueryClient`, pass it via
the `queryClient` prop to share it.

The default `QueryClient` is configured with `staleTime: 30s` and `retry: 1`.

## useSignumQuery

Type-safe queries with the SDK client auto-injected from context:

```tsx title="use-query.tsx"
import { useSignumQuery } from "@signum-tech/sdk-react";

function PartyList() {
  const { data, isLoading, error } = useSignumQuery({
    queryKey: ["parties"],
    queryFn: (client) => client.accounts.getAccounts({}),
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.title}</div>;

  return (
    <ul>
      {data.data.map((party) => (
        <li key={party.id}>{party.displayName}</li>
      ))}
    </ul>
  );
}
```

The `queryFn` receives the SDK client as its first argument — no need to import or
instantiate the client yourself.

The error type defaults to `SignumProblemDetailsError`, so `error.title`, `error.status`,
and other RFC 9457 fields are available without type casting.

## useSignumMutation

Type-safe mutations for write operations:

```tsx title="use-mutation.tsx"
import { useSignumMutation } from "@signum-tech/sdk-react";

function CreateAttestationButton({ partyId }: { partyId: string }) {
  const mutation = useSignumMutation({
    mutationFn: (client, variables: { partyId: string; type: string }) =>
      client.attestations.postAttestations({
        partyId: variables.partyId,
        type: variables.type,
        chainEid: 30101,
      }),
    onSuccess: (data) => {
      console.log("Attestation queued:", data.attestationId);
    },
    onError: (error) => {
      // error is SignumProblemDetailsError by default
      console.error(error.title, error.detail);
    },
  });

  return (
    <button
      onClick={() => mutation.mutate({ partyId, type: "accredited_investor" })}
      disabled={mutation.isPending}
    >
      {mutation.isPending ? "Submitting..." : "Create Attestation"}
    </button>
  );
}
```

The `mutationFn` receives `(client, variables)` — the SDK client from context and the
variables you pass to `mutate()`.

## Polling after a command

Combine `useSignumMutation` with `useSignumQuery` and `refetchInterval` to poll for
command completion:

```tsx title="poll-after-command.tsx"
import { useState } from "react";
import { useSignumMutation, useSignumQuery } from "@signum-tech/sdk-react";

function TransferFlow({ payload }: { payload: TransferPayload }) {
  const [transferId, setTransferId] = useState<string | null>(null);

  const mutation = useSignumMutation({
    mutationFn: (client, vars: TransferPayload) =>
      client.transfers.postTransfers(vars),
    onSuccess: (data) => setTransferId(data.transferId),
  });

  const status = useSignumQuery({
    queryKey: ["transfer", transferId],
    queryFn: (client) =>
      client.transfers.getTransfersByTransferId({
        transferId: transferId!,
      }),
    enabled: !!transferId,
    refetchInterval: (query) =>
      query.state.data?.status === "pending" ? 2000 : false,
  });

  if (status.data?.status === "completed") {
    return <div>Transfer complete!</div>;
  }

  return (
    <button
      onClick={() => mutation.mutate(payload)}
      disabled={mutation.isPending}
    >
      Transfer
    </button>
  );
}
```

## useSignumWallet

The `useSignumWallet` hook manages wallet state across two modes:

* **Managed** — Privy-provisioned wallets with Biconomy gasless UX (no user key management)
* **External** — Bring-your-own wallet via EIP-712 signature binding (e.g., MetaMask, WalletConnect)

```tsx title="wallet-management.tsx"
import { useSignumWallet } from "@signum-tech/sdk-react";

function WalletManager() {
  const {
    walletMode,
    setWalletMode,
    wallets,
    isLoading,
    linkExternalWallet,
    createManagedWallet,
    isLinking,
  } = useSignumWallet();

  return (
    <div>
      <div>
        <button onClick={() => setWalletMode("managed")}>Managed</button>
        <button onClick={() => setWalletMode("external")}>External</button>
      </div>

      <p>Mode: {walletMode}</p>

      {walletMode === "managed" ? (
        <button onClick={() => createManagedWallet()} disabled={isLoading}>
          Create Managed Wallet
        </button>
      ) : (
        <button
          onClick={() =>
            linkExternalWallet(userAddress, async (message) => {
              // signFn — delegate to your wallet provider (wagmi, ethers, etc.)
              return await signer.signMessage(message);
            })
          }
          disabled={isLinking}
        >
          Link External Wallet
        </button>
      )}

      <h3>Wallets</h3>
      <ul>
        {wallets.map((w) => (
          <li key={w.address}>
            {w.address} {w.isPrimary && "(primary)"}
          </li>
        ))}
      </ul>
    </div>
  );
}
```

### External wallet linking flow

When using the external wallet mode, `linkExternalWallet` performs a nonce-sign-link flow:

1. Requests a nonce from the Clossir API
2. Calls your `signFn` with the nonce message
3. Submits the signature to bind the wallet to the user's account

The `signFn` is injected by you — use whatever wallet library your app already has
(wagmi `useSignMessage`, ethers.js `Signer.signMessage`, etc.).

## Re-exports

`@signum-tech/sdk-react` re-exports commonly used types from `@signum-tech/sdk` so you
don't need to import from both packages:

```ts title="re-exports.ts"
import type {
  SDKOptions,
  SignumProblemDetailsError,
} from "@signum-tech/sdk-react";
import { operations, components } from "@signum-tech/sdk-react";
```

Available re-exports:

* `SDKOptions` — client configuration type
* `SignumProblemDetailsError` — the typed error class
* `operations` — all request/response types per endpoint
* `components` — shared schema types

## Next steps

* [Error Handling](/getting-started/sdk/error-handling) — handle API errors in your components
* [Async Operations](/getting-started/sdk/async-operations) — understand the 202 command lifecycle
* [Authentication](/getting-started/authentication) — set up API keys and token exchange