# @edgeandnode/graph-auth-kit

Authentication Kit for connecting The Graph apps/dapps to a wallet.

Relies on/requires [`wagmi`](https://wagmi.sh/) + [`viem`](https://viem.sh/) for allowing users to connect their wallets and interact with the connected auth state context.

Exposes a simple `useGraphAuthKit` hook that lets the app open the connect modal, letting users connect to their wallet of choice (one of: injected/MetaMask, coinbase, WalletConnect, Safe). See integration example below.

## Install

Currently, `graph-auth-kit` has a lot of peer deps needed to interact with the library. But this may change after testing. At the very least, `wagmi, viem, @tanstack/react-query` will be needed.

```bash
# with bun
bun add @edgeandnode/graph-auth-kit \
  @edgeandnode/common \
  @edgeandnode/gds \
  @edgeandnode/go \
  @emotion/react \
  @tanstack/react-query \
  cookies-next \
  ethers@5.7.2 \
  theme-ui \
  viem@2.x \
  wagmi

# with pnpm
pnpm add @edgeandnode/graph-auth-kit \
  @edgeandnode/common \
  @edgeandnode/gds \
  @edgeandnode/go \
  @emotion/react \
  @tanstack/react-query \
  cookies-next \
  ethers@5.7.2 \
  theme-ui \
  viem@2.x \
  wagmi
```

## Usage

Properties:

- `config` -> [REQUIRED]. The instantiated [wagmi config](https://wagmi.sh/react/api/createConfig) instance.
- `queryClient` -> [OPTIONAL]. The `GraphAuthKitProvider` renders a `QueryClientProvider` instance. Pass in an instantiated `QueryClient` to use, otherwise, `GraphAuthKit` will instantiate its own.
- `infuraKey` -> [REQUIRED]
- `gatewayApiKey` -> [OPTIONAL]. A The Graph Gateway API key used to query Subgraphs published on the Graph Network. Used for ENS name resolution

### Example

- Setup the `GraphAuthKitProvider`.
  - **Note** you do not need to add the `ConnectModal` component, the `GraphAuthKitProvider` renders it for us
  - **Note** you do not need to add the `QueryClientProvider` as `GraphAuthKitProvider` instantiates and renders it
    - you can pass a `QueryClient` instance to the `GraphAuthKitProvider`, otherwise, it will instantiate its own

```tsx
// _app.tsx

import { QueryClient } from '@tanstack/react-query'
import { AppProps } from 'next'
import { useRef } from 'react'
import { createClient } from 'viem'
import { cookieStorage, createConfig, createStorage } from 'wagmi'
import { coinbaseWallet, injected, walletConnect } from 'wagmi/connectors'

import { AnalyticsProvider, GDSProvider } from '@edgeandnode/gds'
import {
  AUTH_STORAGE_KEY,
  buildInfuraHttpTransport,
  chainIsSupportedChain,
  DefChain,
  GraphAuthKitProvider,
  L1Chain,
  L1ChainTestnet,
  L2Chain,
  L2ChainTestnet,
} from '@edgeandnode/graph-auth-kit'

import { FutureNextLink } from '../components/FutureNextLink'
import { Layout } from '../components/Layout'

const infuraKey = process.env.INFURA_KEY!
const walletConnectProjectID = process.env.WALLETCONNECT_PROJECT_ID!
const gatewayApiKey = process.env.GATEWAY_API_KEY

import '@edgeandnode/gds/style.css'
import '../app.css'

const config = createConfig({
  chains: [L2Chain, L2ChainTestnet, L1Chain, L1ChainTestnet] as const,
  ssr: typeof window !== 'undefined',
  client(params) {
    const chain = chainIsSupportedChain(params.chain) ? params.chain : DefChain
    const transport = buildInfuraHttpTransport({
      chain: chain.id,
      infuraKey,
    })

    return createClient({
      chain,
      transport,
    })
  },
  connectors: [
    injected(),
    coinbaseWallet({
      appName: 'Boom',
    }),
    walletConnect({
      projectId: env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,
      qrModalOptions: {
        themeMode: 'dark',
      },
    }),
  ],
  storage: createStorage({
    storage: cookieStorage,
    key: AUTH_STORAGE_KEY,
  }),
})

declare module 'wagmi' {
  interface Register {
    config: typeof config
  }
}

export default function App({ Component, router, pageProps }: AppProps) {
  const queryClient = useRef<QueryClient>()
  if (!queryClient.current) {
    queryClient.current = new QueryClient({
      defaultOptions: {
        queries: {
          // With SSR, we usually want to set some default staleTime
          // above 0 to avoid refetching immediately on the client
          staleTime: 60 * 1000,
        },
      },
    })
  }

  return (
    <GDSProvider clientRouter={router} clientLink={FutureNextLink} useThemeUI>
      <AnalyticsProvider
        app="EXPLORER"
        clientRouter={router}
        mixpanel={{
          sdk: mixpanel,
          token: process.env.MIXPANEL_TOKEN ?? null,
        }}
        googleAnalytics={{
          sdk: googleAnalytics,
          measurementId: process.env.GOOGLE_ANALYTICS_MEASUREMENT_ID ?? null,
        }}
      >
        <GraphAuthKitProvider
          config={config}
          queryClient={queryClient.current}
          infuraKey={infuraKey}
          gatewayApiKey={gatewayApiKey}
        >
          <Layout>
            <DefaultSeo {...defaultSEO} />
            <Component {...pageProps} />
          </Layout>
          <ReactQueryDevtools initialIsOpen={false} />
        </GraphAuthKitProvider>
      </AnalyticsProvider>
    </GDSProvider>
  )
}
```

- Usage in a component

```tsx
// components/Layout.tsx

import { ReactNode } from 'react'
import { useAccountEffect, useEnsName } from 'wagmi'

import { Layout as GDSLayout, UserProfile } from '@edgeandnode/gds'
import { GlobalFooter, GlobalHeader, NPSForm } from '@edgeandnode/go'
import { L1Chain, useGraphAuthKitConnector, useGraphAuthKit, useGraphAuthKitAccount } from '@edgeandnode/graph-auth-kit/hooks'

import { NavDropDownMenu } from './NavDropDownMenu'

export function Layout({ children }: Readonly<{ children: ReactNode }>) {
  const authkit = useGraphAuthKit()
  const { address } = useGraphAuthKitAccount()
  const { data: ens } = useEnsName({ address, blockTag: 'latest', chainId: L1Chain.id })
  const connector = useGraphAuthKitConnector()

  const walletConnected = Boolean(address)

  useAccountEffect({
    onConnect(data) {
      console.log('user connected wallet', {data})
    },
    onDisconnect() {
      console.log('user disconnected wallet')
    },
  })

  return (
    <GDSLayout
      header={
        <GlobalHeader
          activeProduct="EXPLORER"
          basePath="/explorer"
          showSupportButton={walletConnected}
          rigtContent={(defaultContent) => (
            <>
              {!walletConnected ?
                <GlobalHeader.ConnectButton onClick={() => authkit.openConnectModal()} />
              ) : (
                <UserProfile
                  ethereumAccount={address}
                  graphAccount={...}
                  ens={ens}
                  profileUrl={`/profile/${address}?view=Overview`}
                  profileUrlAs={`/profile/${address}?view=Overview`}
                  onConnect={() => authkit.openConnectModal()}
                  onDisconnect={() => authkit.disconnect()}
                  userDropDownMenu={<NavDropDownMenu />}
                  accountTag={connector === 'safe' ? 'multisig' : undefined}
                />
              )}
            </>
          )}
        />
      }
      footer={
        <>
          <NPSForm sx={{ mb: Spacing['64px'] }} />
          <GlobalFooter />
        </>
      }
    >
      {children}
    </GDSLayout>
  )
}
```

## Hooks

- `useGraphAuthKitAccount` -> This is an override of the [wagmi useAccount hook](https://wagmi.sh/react/api/hooks/useAccount). The reason is, if the user connects with a multisig, the wagmi context is connected with the user-selected EoA, so all of the wagmi context hooks reference this EoA and not the Safe. This returns the wagmi `UseAccountReturnType` but the `address` and `addresses` values include the entered Safe address. It also adds an `eoa` property that is the connected EoA address.
- `useGraphAuthKitConnector` -> The user selected wallet/connector option.
- `useClientToEthersSigner` -> Returns the ethers@5.x.x JsonRpcSigner or SafeEthersSigner if the user is connected via a multisig
- `useAuthAccount` -> similar to the `useGraphAuthKitAccount` but requires the user to be authenticated and throws an error if not
- `useGraphAuthKitAccountEffect` -> Override of the [wagmi useAccountEffect hook](https://wagmi.sh/react/api/hooks/useAccountEffect) that listens to account changes and the `_enteredMultisigInfo` on the inner context instance. If the account data changes, emits a `onConnect` or `onDisconnect` event, but in the `onConnect` also checks the `useGraphAuthKitInnerContext()._enteredMultisigInfo` value and if the user has connected via a multisig, returns the mutlsig as the `address` value and the connected EoA as the `eoa` value.
- `useGraphAuthKitWalletClient` -> Override of the [wagmi useWalletClient hook](https://wagmi.sh/react/api/hooks/useWalletClient) that returns the extended Safe viem actions if the user is connected to a multisig.
- `useGraphAuthKitWriteContract` -> Override of the [wagmi useWriteContract hook](https://wagmi.sh/react/api/hooks/useWriteContract) that creates the transaction on the Safe if the user is connected via multisig; otherwise, returns the `useWriteContract` mutation hook.

## Components

- `Connected` -> renders the passed in `children` _only if_ the user is connected.

```tsx
import { Connected } from '@edgeandnode/graph-auth-kit'

export function ShowMeConnected() {
  return <Connected>{(account) => <div>Connected Wallet: {account.address}</div>}</Connected>
}
```

- `Disconnected` -> renders the passed in `children` _only if_ the user id disconnected. Useful for rendering components like a "Connect Wallet" CTA that should only render if the user is _not_ authenticated.

```tsx
import { ExperimentalButton as Button } from '@edgeandnode/gds'
import { Disconnected, useGraphAuthKit } from '@edgeandnode/graph-auth-kit'

export function ShowMeDisconnected() {
  const authkit = useGraphAuthKit()

  return (
    <Disconnected>
      <Button variant="primary" onClick={() => authkit.openConnectModal()}>
        Connect Wallet
      </Button>
    </Disconnected>
  )
}
```

- `SwitchChain` -> Similar to the `SwitchNetwork` component from GDS, but uses the [wagmi useSwitchChain hook](https://wagmi.sh/react/api/hooks/useSwitchChain).

```tsx
import { ExperimentalButton as Button } from '@edgeandnode/gds'
import { L2Chain, SwitchChain } from '@edgeandnode/graph-auth-kit'

export function LetMeSwitchChain() {
  return <SwitchChain requestedChain={L2Chain} />
}
```

## References

- [`wagmi`](https://wagmi.sh/)
- [`viem`](https://viem.sh/)
- [Engineering Plan](https://www.notion.so/edgeandnode/Replace-Wallet-Connection-Library-0e4c5e1e015b428dbb52b1f627b712f0)
