# Frontend Implementation (/docs/cookbook/risex-telegram-bot/frontend)



## Building the Frontend

The frontend is a single-page app that handles wallet connection and session key registration.

### Configure Wagmi

Create the wagmi config:

```typescript title="apps/frontend/src/config/wagmi.ts"
import { Chains, RiseWallet, riseWallet } from 'rise-wallet';
import { createConfig, http } from 'wagmi';

export const config = createConfig({
  chains: [Chains.riseTestnet],
  connectors: [riseWallet(RiseWallet.defaultConfig)],
  transports: {
    [Chains.riseTestnet.id]: http('https://testnet.riselabs.xyz'),
  },
});

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

***

### Create RISEx Constants

```typescript title="apps/frontend/src/lib/risex.ts"
export const AUTH_CONTRACT = '0x3f12C76bbeB8df0e7616fBE097fA760A23929c7c';

export const domain = {
  name: 'RISEx',
  version: '1',
  chainId: 8008135,
  verifyingContract: AUTH_CONTRACT as `0x${string}`,
};

export const types = {
  RegisterSigner: [
    { name: 'trader', type: 'address' },
    { name: 'signer', type: 'address' },
    { name: 'expiry', type: 'uint256' },
  ],
};
```

***

### Create Signature Helper

This is critical. RISE Wallet returns wrapped signatures that must be unwrapped:

```typescript title="apps/frontend/src/lib/signature-helper.ts"
import { Signature as OxSignature } from 'ox';
import { SignatureErc8010 } from 'ox/erc8010';

export function formatSignature(sig: string): string {
  // Unwrap ERC-8010 if needed
  if (SignatureErc8010.validate(sig)) {
    const { signature } = SignatureErc8010.unwrap(sig);
    sig = signature;
  }

  // Parse signature
  const { r, s, yParity } = OxSignature.from(sig);

  // Normalize v value
  let v = yParity === 0 ? 27 : 28;

  // Format as 0x + r + s + v
  return `0x${r.slice(2)}${s.slice(2)}${v.toString(16).padStart(2, '0')}`;
}
```

***

## Registration Page

Build the main registration UI:

```typescript title="apps/frontend/src/app/page.tsx"
"use client";

import { useState } from 'react';
import { useConnection, useSignTypedData } from 'wagmi';
import { useConnect, useDisconnect } from 'wagmi';
import { P256, PublicKey } from 'ox';
import { parseAbi } from 'viem';
import { writeContract, waitForTransactionReceipt } from 'wagmi/actions';
import { config } from '@/config/wagmi';
import { AUTH_CONTRACT, domain, types } from '@/lib/risex';
import { formatSignature } from '@/lib/signature-helper';

const AUTH_ABI = parseAbi([
  'function registerSigner(address trader, address signer, uint256 expiry, bytes memory signature) external',
]);

export default function Home() {
  const [telegramId, setTelegramId] = useState('');
  const [status, setStatus] = useState('');
  const [sessionKey, setSessionKey] = useState('');

  const { address } = useConnection();
  const { connect, connectors } = useConnect();
  const { disconnect } = useDisconnect();
  const { signTypedDataAsync } = useSignTypedData();

  const handleRegister = async () => {
    if (!address || !telegramId) {
      setStatus('❌ Connect wallet and enter Telegram ID');
      return;
    }

    try {
      setStatus('Generating session key...');

      // Generate P256 session key
      const privateKey = P256.randomPrivateKey();
      const publicKey = P256.getPublicKey({ privateKey });
      const signerAddress = PublicKey.toAddress(publicKey);

      setSessionKey(PublicKey.toHex(publicKey));

      // Prepare typed data
      const expiry = BigInt(Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60); // 30 days

      setStatus('Sign permission in wallet...');

      // Sign with RISE Wallet
      let signature = await signTypedDataAsync({
        domain,
        types,
        primaryType: 'RegisterSigner',
        message: {
          trader: address,
          signer: signerAddress,
          expiry,
        },
      });

      // Format signature (unwrap and normalize)
      signature = formatSignature(signature);

      setStatus('Registering on-chain...');

      // Call contract
      const hash = await writeContract(config, {
        address: AUTH_CONTRACT,
        abi: AUTH_ABI,
        functionName: 'registerSigner',
        args: [address, signerAddress, expiry, signature as `0x${string}`],
      });

      setStatus('Waiting for confirmation...');
      await waitForTransactionReceipt(config, { hash });

      setStatus('Storing signer...');

      // Store encrypted signer in backend
      await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/store-signer`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          walletAddress: address,
          privateKey,
        }),
      });

      // Link Telegram ID
      await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/link-telegram`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          telegramId,
          walletAddress: address,
        }),
      });

      setStatus('✅ Registration complete! Open your Telegram bot.');
    } catch (error: any) {
      console.error('Registration error:', error);
      setStatus(`❌ Error: ${error.message}`);
    }
  };

  return (
    <div className="min-h-screen bg-black text-white flex items-center justify-center p-4">
      <div className="max-w-md w-full space-y-6">
        <div className="text-center">
          <h1 className="text-4xl font-bold mb-2 bg-gradient-to-r from-purple-400 to-blue-500 text-transparent bg-clip-text">
            RISEx Trading Bot
          </h1>
          <p className="text-gray-400">Register to start trading via Telegram</p>
        </div>

        {!address ? (
          <button
            onClick={() => connect({ connector: connectors[0] })}
            className="w-full py-3 bg-purple-600 hover:bg-purple-700 rounded-lg font-semibold transition"
          >
            Connect RISE Wallet
          </button>
        ) : (
          <div className="space-y-4">
            <div className="p-4 bg-gray-900 rounded-lg">
              <p className="text-sm text-gray-400">Connected:</p>
              <p className="font-mono text-sm">{address.slice(0, 6)}...{address.slice(-4)}</p>
              <button
                onClick={() => disconnect()}
                className="mt-2 text-sm text-red-400 hover:text-red-300"
              >
                Disconnect
              </button>
            </div>

            <div>
              <label className="block text-sm text-gray-400 mb-2">
                Telegram ID (from /debug command)
              </label>
              <input
                type="text"
                value={telegramId}
                onChange={(e) => setTelegramId(e.target.value)}
                placeholder="123456789"
                className="w-full px-4 py-2 bg-gray-900 border border-gray-700 rounded-lg focus:border-purple-500 outline-none"
              />
            </div>

            <button
              onClick={handleRegister}
              disabled={!telegramId || !!status}
              className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded-lg font-semibold transition"
            >
              Register Trading Signer
            </button>

            {status && (
              <div className="p-4 bg-gray-900 rounded-lg">
                <p className="text-sm">{status}</p>
                {sessionKey && (
                  <p className="text-xs text-gray-500 mt-2 break-all">
                    Session Key: {sessionKey}
                  </p>
                )}
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}
```

After connecting and registering, the page should look like this:

<img alt="RISEx Bot Registration Frontend" src={__img0} placeholder="blur" />

***

### Add Provider Wrapper

```typescript title="apps/frontend/src/app/provider.tsx"
"use client";

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { type ReactNode, useState } from 'react';
import { WagmiProvider } from 'wagmi';
import { config } from '@/config/wagmi';

export function Provider(props: { children: ReactNode }) {
  const [queryClient] = useState(() => new QueryClient());

  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        {props.children}
      </QueryClientProvider>
    </WagmiProvider>
  );
}
```

Update layout:

```typescript title="apps/frontend/src/app/layout.tsx"
import type { Metadata } from 'next';
import './globals.css';
import { Provider } from './provider';

export const metadata: Metadata = {
  title: 'RISEx Trading Bot',
  description: 'Register for AI-powered Telegram trading',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Provider>{children}</Provider>
      </body>
    </html>
  );
}
```

Your frontend is complete! Next, we'll [test everything](/docs/cookbook/risex-telegram-bot/testing).
