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;