better-auth-solana
v1.0.0
Published
Better Auth plugin for Solana sign-in.
Downloads
1,438
Maintainers
Readme
better-auth-solana
better-auth-solana is a Better Auth plugin for Sign in With Solana.
It uses the Better Auth plugin namespace siws and requires better-auth ^1.5.0.
It exposes three public entrypoints:
better-auth-solanafor the server pluginbetter-auth-solana/clientfor client helpersbetter-auth-solana/schemafor the default Solana wallet schema
AI Agent Skill
If you use AI coding agents such as Codex, Claude Code, or OpenCode in a skills-enabled setup, install the companion skill with:
npx skills add beeman/better-auth-solanaYou can also inspect the packaged skill directly in node_modules at skills/better-auth-solana/SKILL.md.
For deeper integration guidance, see:
Install
bun add @solana/kit better-auth better-auth-solana@latestServer
import { betterAuth } from 'better-auth'
import { siws } from 'better-auth-solana'
export const auth = betterAuth({
database: myDatabase,
plugins: [
siws({
domain: 'example.com',
}),
],
})The plugin adds these Better Auth endpoints:
POST /siws/linkPOST /siws/noncePOST /siws/verify
These are plugin subpaths under your Better Auth handler. If Better Auth is mounted under /api/auth/*, for example, POST /siws/verify becomes POST /api/auth/siws/verify.
POST /siws/link accepts { walletAddress, message, signature }, requires an authenticated Better Auth session, verifies the signed SIWS payload, persists a solanaWallet row when needed, persists an account row with providerId: "siws" when needed, and keeps the current session intact.
POST /siws/nonce accepts { walletAddress } and stores a challenge in Better Auth verification storage under siws:<walletAddress>.
POST /siws/verify accepts { walletAddress, message, signature, email? }, verifies the signed SIWS payload, creates or reuses the Better Auth user, persists a solanaWallet row, persists an account row with providerId: "siws", and establishes the Better Auth session with native session cookies.
For backend wiring, cookies, and origin handling, see Backend.
Client
better-auth-solana/client exports createSIWSInput(...), createSIWSMessage(...), formatSIWSMessage(...), and siwsClient() for Better Auth client inference.
- Use
createSIWSInput(...)when the wallet library accepts structured SIWS input. - Use
createSIWSMessage(...)when the wallet library expects the raw SIWS message string. - Use
formatSIWSMessage(...)when you already have the SIWS fields and only need the canonical message string.
Use authClient.siws.verify(...) for SIWS sign-in when the wallet flow should create or resume the Better Auth session. Use authClient.siws.link(...) only when the user already has a Better Auth session and wants to attach another wallet.
import { createAuthClient } from 'better-auth/client'
import { createSIWSInput, siwsClient } from 'better-auth-solana/client'
const authClient = createAuthClient({
plugins: [siwsClient()],
})
const nonceResult = await authClient.siws.nonce({
walletAddress: address,
})
if (!nonceResult.data) {
throw new Error('Failed to request SIWS nonce')
}
const siwsInput = createSIWSInput({
address,
challenge: nonceResult.data,
statement: 'Sign in to Example',
})
const signed = await signWithYourWallet(siwsInput)
await authClient.siws.verify({
message: signed.message,
signature: signed.signature,
walletAddress: address,
})
const session = await authClient.getSession()For raw signMessage flows, build the SIWS string directly:
import { createSIWSMessage } from 'better-auth-solana/client'
const message = createSIWSMessage({
address,
challenge: nonceResult.data,
statement: 'Sign in to Example',
})
const signed = await signMessage(new TextEncoder().encode(message))The server validates the signed message against the issued domain, uri, nonce, issuedAt, and expirationTime. The statement remains client-controlled.
For platform-specific client guidance, see React Native and Expo and React web.
Default Schema
import { solanaWalletSchema } from 'better-auth-solana/schema'The default solanaWallet model defines these custom fields:
addresscreatedAtisPrimaryuserId
Better Auth adapters commonly add a generated primary key such as id, so the list above describes the plugin-defined fields rather than the full persisted table shape. The default schema marks address as unique. The plugin stores one wallet row per address and one SIWS account row per wallet address.
The first Solana wallet row created for a user is marked isPrimary: true. Additional Solana wallet rows for that user are marked isPrimary: false.
SIWS also depends on Better Auth's standard account, session, user, and verification tables. The package writes solanaWallet directly and uses Better Auth internals for the other models.
For schema composition and table expectations, see Schema and tables.
Options
siws() accepts:
anonymousdefaulttruedomainrequiredemailDomainNameoptionalgetNonceoptional async override for SIWS nonce generationnonceExpirationMsdefault900000profileLookupoptional async lookup for usernameandimagewhen SIWS creates a brand-new userschemaoptional Better Auth schema field overrides forsolanaWalleturioptional override for the SIWS challengeuriverifySignatureoptional async override for SIWS signature verification
When anonymous is false, email is required on /siws/verify.
When emailDomainName is omitted, generated fallback emails use the Better Auth base URL host.
When profileLookup is provided, it is only used when SIWS creates a brand-new Better Auth user. Existing users are not updated on later sign-ins or links.
When uri is a non-empty string, issued challenges use it verbatim. Otherwise SIWS uses Better Auth's resolved base URL and falls back to https://${domain} when no base URL is available.
Notes
- The account id format is
<walletAddress>. - The account provider id is
siws. - The client surface is
authClient.siws.link(...),authClient.siws.nonce(...), andauthClient.siws.verify(...). - If your app already uses Better Auth's
last-login-methodplugin, add acustomResolveMethodthat maps/siws/verifytosiws. - The package name is
better-auth-solana; the plugin namespace issiws.
The Better Auth docs currently describe default last-login-method detection for email and OAuth flows. Because SIWS is a custom flow, map it explicitly when you use that plugin. See Better Auth's Last Login Method and SIWE docs for the surrounding Better Auth behavior.
Example last-login-method resolver:
import { lastLoginMethod } from 'better-auth/plugins'
lastLoginMethod({
customResolveMethod(ctx) {
if (ctx.path === '/siws/verify') {
return 'siws'
}
return null
},
})Development
bun install
bun run build
bun run check-types
bun run lint
bun run lint:fix
bun run test
bun run test:watch