/ src / index.ts
index.ts
  1  import { Hono } from "hono";
  2  import * as ed from "@noble/ed25519";
  3  import { mnemonicToAccount } from "viem/accounts";
  4  import { Buffer } from 'node:buffer';
  5  import qrcode from 'qrcode-generator';
  6  
  7  
  8  const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {
  9    name: "Farcaster SignedKeyRequestValidator",
 10    version: "1",
 11    chainId: 10,
 12    verifyingContract: "0x00000000fc700472606ed4fa22623acf62c60553",
 13  } as const;
 14  
 15  const SIGNED_KEY_REQUEST_TYPE = [
 16    { name: "requestFid", type: "uint256" },
 17    { name: "key", type: "bytes" },
 18    { name: "deadline", type: "uint256" },
 19  ] as const;
 20  
 21  type Bindings = {
 22    FARCASTER_DEVELOPER_FID: string;
 23    FARCASTER_DEVELOPER_MNEMONIC: string;
 24  }
 25  
 26  const app = new Hono<{ Bindings: Bindings }>();
 27  
 28  app.get("/", (c) => {
 29    return c.text("Hello Hono!");
 30  });
 31  
 32  app.post("/sign-in", async (c) => {
 33    try {
 34      const signInWithWarpcast = async () => {
 35        const privateKeyBytes = ed.utils.randomPrivateKey();
 36        const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
 37  
 38        const keypairString = {
 39          publicKey: "0x" + Buffer.from(publicKeyBytes).toString("hex"),
 40          privateKey: "0x" + Buffer.from(privateKeyBytes).toString("hex"),
 41        };
 42        const appFid = c.env.FARCASTER_DEVELOPER_FID!;
 43        const account = mnemonicToAccount(
 44          c.env.FARCASTER_DEVELOPER_MNEMONIC!,
 45        );
 46  
 47        const deadline = Math.floor(Date.now() / 1000) + 86400; // signature is valid for 1 day
 48        const requestFid = parseInt(appFid);
 49        const signature = await account.signTypedData({
 50          domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN,
 51          types: {
 52            SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE,
 53          },
 54          primaryType: "SignedKeyRequest",
 55          message: {
 56            requestFid: BigInt(appFid),
 57            key: keypairString.publicKey as `0x`,
 58            deadline: BigInt(deadline),
 59          },
 60        });
 61        const authData = {
 62          signature: signature,
 63          requestFid: requestFid,
 64          deadline: deadline,
 65          requestSigner: account.address,
 66        };
 67        const {
 68          result: { signedKeyRequest },
 69        } = (await (
 70          await fetch(`https://api.warpcast.com/v2/signed-key-requests`, {
 71            method: "POST",
 72            headers: {
 73              "Content-Type": "application/json",
 74            },
 75            body: JSON.stringify({
 76              key: keypairString.publicKey,
 77              signature,
 78              requestFid,
 79              deadline,
 80            }),
 81          })
 82        ).json()) as {
 83          result: { signedKeyRequest: { token: string; deeplinkUrl: string } };
 84        };
 85        const user: any = {
 86          ...authData,
 87          publicKey: keypairString.publicKey,
 88          deadline: deadline,
 89          token: signedKeyRequest.token,
 90          signerApprovalUrl: signedKeyRequest.deeplinkUrl,
 91          privateKey: keypairString.privateKey,
 92          status: "pending_approval",
 93        };
 94        return user;
 95      };
 96  
 97      const signInData = await signInWithWarpcast();
 98      if (!signInData) {
 99        return c.json({ error: "Failed to sign in user" }, { status: 500 });
100      }
101      if (signInData) {
102        return c.json({
103          deepLinkUrl: signInData?.signerApprovalUrl,
104          pollingToken: signInData?.token,
105          publicKey: signInData?.publicKey,
106          privateKey: signInData?.privateKey,
107        });
108      } else {
109        return c.json({ error: "Failed to get farcaster user" }, {status: 500});
110      }
111    } catch (error) {
112      console.log(error)
113      return c.json({ error: error }, {status: 500});
114    }
115  });
116  
117  app.get("/sign-in/poll", async (c) => {
118    const pollingToken = c.req.query('token');
119      try {
120        const fcSignerRequestResponse = await fetch(
121          `https://api.warpcast.com/v2/signed-key-request?token=${pollingToken}`,
122          {
123            method: "GET",
124            headers: {
125              "Content-Type": "application/json",
126            },
127          }
128        );
129        const responseBody = (await fcSignerRequestResponse.json()) as {
130          result: { signedKeyRequest:  any};
131        };
132        console.log(responseBody)
133        return c.json({"state": responseBody.result.signedKeyRequest.state, "userFid": responseBody.result.signedKeyRequest.userFid}, { status: 200});
134      }
135      catch (error) {
136        return c.json({ error: error}, {status: 500});
137      }
138    }
139  );
140  
141  
142  app.get('/qr/:token', (c) => {
143    const token = c.req.param('token');
144    try {
145      let qr = qrcode(0, 'H');
146      qr.addData(`farcaster://signed-key-request?token=${token}`);
147      qr.make();
148      const qrCodeDataURL = qr.createDataURL(6, 4);
149  
150      const imageData = qrCodeDataURL.split(',')[1];
151  
152      const imageBuffer = Uint8Array.from(atob(imageData), c => c.charCodeAt(0));
153  
154      return new Response(imageBuffer, {
155        headers: {
156          'Content-Type': 'image/png'
157        }
158      });
159    } catch (error) {
160      console.error(error);
161      return c.json({ error: "Failed to generate QR code" }, { status: 500 });
162    }
163  });
164  
165  export default app;