{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createAuthEndpoint } from \"better-auth/api\";\nimport type { BetterAuthPlugin, User } from \"better-auth\";\nimport bs58 from \"bs58\";\nimport * as ed25519 from \"@noble/ed25519\";\nimport { z } from \"zod/v3\";\nimport { setSessionCookie } from \"better-auth/cookies\";\n\n/* -------------------------------- Options -------------------------------- */\n\nexport interface SiwsOptions {\n  domain: string;            // e.g., \"app.example.com\" (no protocol)\n  statement?: string;\n  nonceTtlSeconds?: number;  // default 300\n}\n\n/* ------------------------- Canonical message builder ---------------------- */\n\nexport function buildSiwsMessage(i: {\n  domain: string;\n  address: string;      // base58\n  uri: string;\n  statement?: string;\n  nonce: string;\n  issuedAt: string;        // ISO\n  expirationTime?: string; // ISO\n  resources?: string[];\n}) {\n  const lines = [\n    `${i.domain} wants you to sign in with your Solana account:`,\n    `${i.address}`,\n    \"\",\n    i.statement ?? \"Sign in with Solana to the app.\",\n    \"\",\n    `URI: ${i.uri}`,\n    `Version: 1`,\n    `Nonce: ${i.nonce}`,\n    `Issued At: ${i.issuedAt}`,\n  ];\n  if (i.expirationTime) lines.push(`Expiration Time: ${i.expirationTime}`);\n  if (i.resources?.length) lines.push(`Resources:\\n- ${i.resources.join(\"\\n- \")}`);\n  return lines.join(\"\\n\");\n}\n\n/* ------------------------------ Server plugin ---------------------------- */\n\nexport const siwsPlugin = (options: SiwsOptions) =>\n({\n  id: \"siws\",\n  endpoints: {\n    // POST /siws/start -> { nonce, domain, uri }\n    start: createAuthEndpoint(\"/siws/start\", {\n      method: \"POST\", body: z.object({\n        address: z.string().min(32),\n      }),\n    }, async (ctx) => {\n      const { address } = ctx.body;\n\n      const nonce = bs58.encode(crypto.getRandomValues(new Uint8Array(16)));\n      const expiresAt = new Date(\n        Date.now() + (options.nonceTtlSeconds ?? 300) * 1000\n      );\n\n      await ctx.context.internalAdapter.createVerificationValue({\n        identifier: `siws:${address}`,\n        value: nonce,\n        expiresAt,\n      });\n\n      return ctx.json({\n        nonce,\n        domain: options.domain,\n        uri: ctx.context.baseURL,\n      });\n    }),\n\n    // POST /siws/verify -> verify signature, bind domain, upsert user, create session\n    verify: createAuthEndpoint(\"/siws/verify\", {\n      method: \"POST\", body: z.object({\n        address: z.string().min(32),\n        message: z.string(),\n        signature: z.string(),\n      }),\n    }, async (ctx) => {\n      const { address, message, signature } = ctx.body;\n\n      // 1) Extract nonce from message\n      const nonceLine = message.split(\"\\n\").find((l: string) => l.startsWith(\"Nonce: \"));\n      const nonceFromMsg = nonceLine?.slice(\"Nonce: \".length).trim();\n      if (!nonceFromMsg) return new Response(\"Nonce missing\", { status: 400 });\n\n      // 2) Find verification value\n      const v = await ctx.context.internalAdapter.findVerificationValue(`siws:${address}`);\n      if (!v || new Date(v.expiresAt) <= new Date()) {\n        return new Response(\"Nonce invalid or expired\", { status: 400 });\n      }\n\n      // 3) Delete it to enforce single-use\n      await ctx.context.internalAdapter.deleteVerificationValue(`siws:${address}`);\n\n      // 4) Domain binding\n      const expectedDomain = options.domain;\n      if (!message.startsWith(`${expectedDomain} wants you to sign in`)) {\n        return new Response(\"Domain mismatch\", { status: 400 });\n      }\n\n      // 5) Verify ed25519 signature\n      const verified = await ed25519.verifyAsync(\n        bs58.decode(signature),\n        new TextEncoder().encode(message),\n        bs58.decode(address),\n      );\n      if (!verified) return new Response(\"Invalid signature\", { status: 401 });\n\n      // 6) Upsert user + create session\n      const existingAccount = await ctx.context.internalAdapter.findAccount(buildAccountId(address));\n\n      let userObject: User;\n      if (!existingAccount) {\n        const user = await ctx.context.internalAdapter.createOAuthUser({\n          email: address,\n          emailVerified: true,\n          name: `sol:${address.slice(0, 4)}…${address.slice(-4)}`,\n        }, {\n          providerId: \"siws\",\n          accountId: buildAccountId(address),\n        }, ctx);\n        userObject = user.user;\n      } else {\n        const user = await ctx.context.internalAdapter.findUserById(existingAccount.userId);\n        userObject = user!;\n      }\n\n      const session = await ctx.context.internalAdapter.createSession(userObject.id, ctx);\n      await setSessionCookie(ctx, { session, user: userObject });\n\n      return ctx.json({ user: userObject.id, session });\n    }),\n  },\n} satisfies BetterAuthPlugin);\n\nconst buildAccountId = (address: string) => {\n  return `siws:${address}`;\n};\n\n\n"],"mappings":"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","names":["bs58"]}