@pinklemon8/better-auth-siws 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,335 @@
1
+ // src/react/provider.tsx
2
+ import { useMemo } from "react";
3
+ import {
4
+ ConnectionProvider,
5
+ WalletProvider
6
+ } from "@solana/wallet-adapter-react";
7
+ import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
8
+ import { clusterApiUrl } from "@solana/web3.js";
9
+ import "@solana/wallet-adapter-react-ui/styles.css";
10
+ import { jsx } from "react/jsx-runtime";
11
+ var SolanaProvider = ({
12
+ children,
13
+ cluster = "mainnet-beta",
14
+ endpoint,
15
+ autoConnect = false
16
+ }) => {
17
+ const rpcEndpoint = useMemo(
18
+ () => endpoint ?? clusterApiUrl(cluster),
19
+ [endpoint, cluster]
20
+ );
21
+ const wallets = useMemo(() => [], []);
22
+ return /* @__PURE__ */ jsx(ConnectionProvider, { endpoint: rpcEndpoint, children: /* @__PURE__ */ jsx(WalletProvider, { wallets, autoConnect, children: /* @__PURE__ */ jsx(WalletModalProvider, { children }) }) });
23
+ };
24
+
25
+ // src/react/sign-in.tsx
26
+ import { useEffect, useState } from "react";
27
+ import { useWallet } from "@solana/wallet-adapter-react";
28
+ import { useWalletModal } from "@solana/wallet-adapter-react-ui";
29
+
30
+ // src/types.ts
31
+ function serializeOutput(output) {
32
+ return {
33
+ account: {
34
+ address: output.account.address,
35
+ publicKey: Array.from(output.account.publicKey)
36
+ },
37
+ signature: Array.from(output.signature),
38
+ signedMessage: Array.from(output.signedMessage),
39
+ signatureType: output.signatureType
40
+ };
41
+ }
42
+
43
+ // src/react/sign-in.tsx
44
+ import { jsx as jsx2 } from "react/jsx-runtime";
45
+ function SolanaSignInInner({
46
+ baseURL = "",
47
+ onSuccess,
48
+ onError,
49
+ className,
50
+ children
51
+ }) {
52
+ const { publicKey, connected, disconnect, wallet, connecting, connect } = useWallet();
53
+ const { setVisible, visible } = useWalletModal();
54
+ const [loading, setLoading] = useState(false);
55
+ const [signingIn, setSigningIn] = useState(false);
56
+ const [hasTriggered, setHasTriggered] = useState(false);
57
+ useEffect(() => {
58
+ if (connected && publicKey && signingIn && !hasTriggered) {
59
+ setHasTriggered(true);
60
+ setVisible(false);
61
+ handleSignIn();
62
+ }
63
+ }, [connected, publicKey, signingIn, wallet, hasTriggered]);
64
+ useEffect(() => {
65
+ if (wallet && !connected && !connecting && signingIn) {
66
+ connect().catch(console.error);
67
+ }
68
+ }, [wallet, connected, connecting, signingIn, connect]);
69
+ useEffect(() => {
70
+ if (!visible && signingIn && !connected && !connecting && !wallet) {
71
+ setSigningIn(false);
72
+ setHasTriggered(false);
73
+ }
74
+ }, [visible, signingIn, connected, connecting, wallet]);
75
+ const handleClick = async () => {
76
+ setSigningIn(true);
77
+ if (wallet && !connected) {
78
+ try {
79
+ await connect();
80
+ } catch {
81
+ setVisible(true);
82
+ }
83
+ } else if (!wallet) {
84
+ setVisible(true);
85
+ }
86
+ };
87
+ const handleSignIn = async () => {
88
+ if (!publicKey || !wallet?.adapter) {
89
+ setSigningIn(false);
90
+ return;
91
+ }
92
+ const adapter = wallet.adapter;
93
+ if (!("signIn" in adapter) || typeof adapter.signIn !== "function") {
94
+ onError?.(
95
+ "Your wallet does not support Sign In With Solana. Please use Phantom v23.11.0 or later."
96
+ );
97
+ setSigningIn(false);
98
+ disconnect();
99
+ return;
100
+ }
101
+ setLoading(true);
102
+ try {
103
+ const nonceRes = await fetch(`${baseURL}/api/auth/siws/nonce`, {
104
+ method: "POST",
105
+ headers: { "Content-Type": "application/json" },
106
+ body: JSON.stringify({ walletAddress: publicKey.toBase58() })
107
+ });
108
+ const nonceData = await nonceRes.json();
109
+ const input = {
110
+ domain: nonceData.domain,
111
+ statement: nonceData.statement,
112
+ uri: nonceData.uri,
113
+ nonce: nonceData.nonce,
114
+ chainId: nonceData.chainId,
115
+ issuedAt: nonceData.issuedAt
116
+ };
117
+ const output = await adapter.signIn(input);
118
+ const serialized = serializeOutput(output);
119
+ const verifyRes = await fetch(`${baseURL}/api/auth/siws/verify`, {
120
+ method: "POST",
121
+ headers: { "Content-Type": "application/json" },
122
+ body: JSON.stringify({ input, output: serialized })
123
+ });
124
+ const data = await verifyRes.json();
125
+ if (!verifyRes.ok) {
126
+ onError?.(data.message || data.error || "Failed to sign in with Solana");
127
+ } else {
128
+ onSuccess?.(data.user);
129
+ }
130
+ } catch (error) {
131
+ if (error?.message !== "User rejected the request.") {
132
+ onError?.(error?.message || "Failed to sign in with Solana wallet");
133
+ }
134
+ } finally {
135
+ setLoading(false);
136
+ setSigningIn(false);
137
+ setHasTriggered(false);
138
+ disconnect();
139
+ }
140
+ };
141
+ return /* @__PURE__ */ jsx2(
142
+ "button",
143
+ {
144
+ onClick: handleClick,
145
+ disabled: loading,
146
+ className,
147
+ type: "button",
148
+ children: children ?? (loading ? "Authenticating..." : "Sign In with Solana")
149
+ }
150
+ );
151
+ }
152
+ var SolanaSignInButton = ({
153
+ cluster,
154
+ endpoint,
155
+ ...props
156
+ }) => {
157
+ return /* @__PURE__ */ jsx2(SolanaProvider, { cluster, endpoint, children: /* @__PURE__ */ jsx2(SolanaSignInInner, { ...props }) });
158
+ };
159
+
160
+ // src/react/link-wallet.tsx
161
+ import { useEffect as useEffect2, useState as useState2 } from "react";
162
+ import { useWallet as useWallet2 } from "@solana/wallet-adapter-react";
163
+ import { useWalletModal as useWalletModal2 } from "@solana/wallet-adapter-react-ui";
164
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
165
+ function LinkWalletInner({
166
+ baseURL = "",
167
+ onLink,
168
+ onUnlink,
169
+ onError,
170
+ className,
171
+ unlinkClassName,
172
+ renderLinked,
173
+ children
174
+ }) {
175
+ const { publicKey, connected, disconnect, wallet, connecting, connect } = useWallet2();
176
+ const { setVisible, visible } = useWalletModal2();
177
+ const [linkedWallet, setLinkedWallet] = useState2(null);
178
+ const [loading, setLoading] = useState2(true);
179
+ const [linking, setLinking] = useState2(false);
180
+ const [hasTriggered, setHasTriggered] = useState2(false);
181
+ const fetchWallets = async () => {
182
+ try {
183
+ const res = await fetch(`${baseURL}/api/auth/siws/wallets`);
184
+ const data = await res.json();
185
+ if (data.wallets?.[0]) {
186
+ setLinkedWallet(data.wallets[0].walletAddress);
187
+ }
188
+ } catch {
189
+ }
190
+ setLoading(false);
191
+ };
192
+ useEffect2(() => {
193
+ fetchWallets();
194
+ }, []);
195
+ useEffect2(() => {
196
+ if (connected && publicKey && linking && !hasTriggered) {
197
+ setHasTriggered(true);
198
+ setVisible(false);
199
+ handleLink();
200
+ }
201
+ }, [connected, publicKey, linking, wallet, hasTriggered]);
202
+ useEffect2(() => {
203
+ if (wallet && !connected && !connecting && linking) {
204
+ connect().catch(console.error);
205
+ }
206
+ }, [wallet, connected, connecting, linking, connect]);
207
+ useEffect2(() => {
208
+ if (!visible && linking && !connected && !connecting && !wallet) {
209
+ setLinking(false);
210
+ setHasTriggered(false);
211
+ }
212
+ }, [visible, linking, connected, connecting, wallet]);
213
+ const handleLinkClick = async () => {
214
+ setLinking(true);
215
+ if (wallet && !connected) {
216
+ try {
217
+ await connect();
218
+ } catch {
219
+ setVisible(true);
220
+ }
221
+ } else if (!wallet) {
222
+ setVisible(true);
223
+ }
224
+ };
225
+ const handleLink = async () => {
226
+ if (!wallet?.adapter) return;
227
+ const adapter = wallet.adapter;
228
+ if (!("signIn" in adapter)) {
229
+ onError?.("Wallet doesn't support SIWS");
230
+ setLinking(false);
231
+ disconnect();
232
+ return;
233
+ }
234
+ try {
235
+ const createRes = await fetch(`${baseURL}/api/auth/siws/nonce`, {
236
+ method: "POST",
237
+ headers: { "Content-Type": "application/json" },
238
+ body: JSON.stringify({
239
+ walletAddress: publicKey.toBase58()
240
+ })
241
+ });
242
+ const nonceData = await createRes.json();
243
+ const input = {
244
+ domain: nonceData.domain,
245
+ statement: nonceData.statement,
246
+ uri: nonceData.uri,
247
+ nonce: nonceData.nonce,
248
+ chainId: nonceData.chainId,
249
+ issuedAt: nonceData.issuedAt
250
+ };
251
+ const output = await adapter.signIn(input);
252
+ const serializedOutput = {
253
+ account: {
254
+ address: output.account.address,
255
+ publicKey: Array.from(output.account.publicKey)
256
+ },
257
+ signature: Array.from(output.signature),
258
+ signedMessage: Array.from(output.signedMessage)
259
+ };
260
+ const linkRes = await fetch(`${baseURL}/api/auth/siws/link`, {
261
+ method: "POST",
262
+ headers: { "Content-Type": "application/json" },
263
+ body: JSON.stringify({ input, output: serializedOutput })
264
+ });
265
+ if (linkRes.ok) {
266
+ const data = await linkRes.json();
267
+ setLinkedWallet(data.walletAddress);
268
+ onLink?.(data.walletAddress);
269
+ } else {
270
+ const err = await linkRes.json();
271
+ onError?.(err.message || "Failed to link wallet");
272
+ }
273
+ } catch (e) {
274
+ if (!e?.message?.includes("rejected")) {
275
+ onError?.("Failed to link wallet");
276
+ }
277
+ } finally {
278
+ setLinking(false);
279
+ setHasTriggered(false);
280
+ disconnect();
281
+ }
282
+ };
283
+ const handleUnlink = async () => {
284
+ if (!linkedWallet) return;
285
+ try {
286
+ const res = await fetch(`${baseURL}/api/auth/siws/unlink`, {
287
+ method: "POST",
288
+ headers: { "Content-Type": "application/json" },
289
+ body: JSON.stringify({ walletAddress: linkedWallet })
290
+ });
291
+ if (res.ok) {
292
+ setLinkedWallet(null);
293
+ onUnlink?.();
294
+ }
295
+ } catch {
296
+ onError?.("Failed to unlink wallet");
297
+ }
298
+ };
299
+ if (loading) {
300
+ return /* @__PURE__ */ jsx3("button", { disabled: true, className, children: "Loading..." });
301
+ }
302
+ if (linkedWallet) {
303
+ if (renderLinked) {
304
+ return /* @__PURE__ */ jsx3(Fragment, { children: renderLinked({ address: linkedWallet, onUnlink: handleUnlink }) });
305
+ }
306
+ const truncated = `${linkedWallet.slice(0, 4)}...${linkedWallet.slice(-4)}`;
307
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
308
+ /* @__PURE__ */ jsx3("span", { style: { fontFamily: "monospace", fontSize: "0.875rem" }, children: truncated }),
309
+ /* @__PURE__ */ jsx3("button", { onClick: handleUnlink, className: unlinkClassName, type: "button", children: "Unlink" })
310
+ ] });
311
+ }
312
+ return /* @__PURE__ */ jsx3(
313
+ "button",
314
+ {
315
+ onClick: handleLinkClick,
316
+ disabled: linking,
317
+ className,
318
+ type: "button",
319
+ children: children ?? (linking ? "Linking..." : "Link Solana Wallet")
320
+ }
321
+ );
322
+ }
323
+ var SolanaLinkWallet = ({
324
+ cluster,
325
+ endpoint,
326
+ ...props
327
+ }) => {
328
+ return /* @__PURE__ */ jsx3(SolanaProvider, { cluster, endpoint, children: /* @__PURE__ */ jsx3(LinkWalletInner, { ...props }) });
329
+ };
330
+ export {
331
+ SolanaLinkWallet,
332
+ SolanaProvider,
333
+ SolanaSignInButton
334
+ };
335
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/provider.tsx","../../src/react/sign-in.tsx","../../src/types.ts","../../src/react/link-wallet.tsx"],"sourcesContent":["\"use client\";\n\nimport { type FC, type ReactNode, useMemo } from \"react\";\nimport {\n ConnectionProvider,\n WalletProvider,\n} from \"@solana/wallet-adapter-react\";\nimport { WalletModalProvider } from \"@solana/wallet-adapter-react-ui\";\nimport { clusterApiUrl } from \"@solana/web3.js\";\n\nimport \"@solana/wallet-adapter-react-ui/styles.css\";\n\nexport type SolanaProviderProps = {\n children: ReactNode;\n cluster?: \"mainnet-beta\" | \"devnet\" | \"testnet\";\n endpoint?: string;\n autoConnect?: boolean;\n};\n\nexport const SolanaProvider: FC<SolanaProviderProps> = ({\n children,\n cluster = \"mainnet-beta\",\n endpoint,\n autoConnect = false,\n}) => {\n const rpcEndpoint = useMemo(\n () => endpoint ?? clusterApiUrl(cluster),\n [endpoint, cluster]\n );\n const wallets = useMemo(() => [], []);\n\n return (\n <ConnectionProvider endpoint={rpcEndpoint}>\n <WalletProvider wallets={wallets} autoConnect={autoConnect}>\n <WalletModalProvider>{children}</WalletModalProvider>\n </WalletProvider>\n </ConnectionProvider>\n );\n};\n","\"use client\";\n\nimport { useEffect, useState, type FC } from \"react\";\nimport { useWallet } from \"@solana/wallet-adapter-react\";\nimport { useWalletModal } from \"@solana/wallet-adapter-react-ui\";\nimport type { SolanaSignInInput } from \"@solana/wallet-standard-features\";\nimport { serializeOutput } from \"../types\";\nimport { SolanaProvider, type SolanaProviderProps } from \"./provider\";\n\nexport type SolanaSignInButtonProps = {\n baseURL?: string;\n onSuccess?: (user: { id: string; name: string; email: string }) => void;\n onError?: (error: string) => void;\n className?: string;\n children?: React.ReactNode;\n cluster?: SolanaProviderProps[\"cluster\"];\n endpoint?: SolanaProviderProps[\"endpoint\"];\n};\n\nfunction SolanaSignInInner({\n baseURL = \"\",\n onSuccess,\n onError,\n className,\n children,\n}: Omit<SolanaSignInButtonProps, \"cluster\" | \"endpoint\">) {\n const { publicKey, connected, disconnect, wallet, connecting, connect } =\n useWallet();\n const { setVisible, visible } = useWalletModal();\n const [loading, setLoading] = useState(false);\n const [signingIn, setSigningIn] = useState(false);\n const [hasTriggered, setHasTriggered] = useState(false);\n\n useEffect(() => {\n if (connected && publicKey && signingIn && !hasTriggered) {\n setHasTriggered(true);\n setVisible(false);\n handleSignIn();\n }\n }, [connected, publicKey, signingIn, wallet, hasTriggered]);\n\n useEffect(() => {\n if (wallet && !connected && !connecting && signingIn) {\n connect().catch(console.error);\n }\n }, [wallet, connected, connecting, signingIn, connect]);\n\n useEffect(() => {\n if (!visible && signingIn && !connected && !connecting && !wallet) {\n setSigningIn(false);\n setHasTriggered(false);\n }\n }, [visible, signingIn, connected, connecting, wallet]);\n\n const handleClick = async () => {\n setSigningIn(true);\n if (wallet && !connected) {\n try {\n await connect();\n } catch {\n setVisible(true);\n }\n } else if (!wallet) {\n setVisible(true);\n }\n };\n\n const handleSignIn = async () => {\n if (!publicKey || !wallet?.adapter) {\n setSigningIn(false);\n return;\n }\n\n const adapter = wallet.adapter as any;\n if (!(\"signIn\" in adapter) || typeof adapter.signIn !== \"function\") {\n onError?.(\n \"Your wallet does not support Sign In With Solana. Please use Phantom v23.11.0 or later.\"\n );\n setSigningIn(false);\n disconnect();\n return;\n }\n\n setLoading(true);\n\n try {\n const nonceRes = await fetch(`${baseURL}/api/auth/siws/nonce`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ walletAddress: publicKey.toBase58() }),\n });\n const nonceData = await nonceRes.json();\n\n const input: SolanaSignInInput = {\n domain: nonceData.domain,\n statement: nonceData.statement,\n uri: nonceData.uri,\n nonce: nonceData.nonce,\n chainId: nonceData.chainId,\n issuedAt: nonceData.issuedAt,\n };\n\n const output = await adapter.signIn(input);\n const serialized = serializeOutput(output);\n\n const verifyRes = await fetch(`${baseURL}/api/auth/siws/verify`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ input, output: serialized }),\n });\n\n const data = await verifyRes.json();\n\n if (!verifyRes.ok) {\n onError?.(data.message || data.error || \"Failed to sign in with Solana\");\n } else {\n onSuccess?.(data.user);\n }\n } catch (error: any) {\n if (error?.message !== \"User rejected the request.\") {\n onError?.(error?.message || \"Failed to sign in with Solana wallet\");\n }\n } finally {\n setLoading(false);\n setSigningIn(false);\n setHasTriggered(false);\n disconnect();\n }\n };\n\n return (\n <button\n onClick={handleClick}\n disabled={loading}\n className={className}\n type=\"button\"\n >\n {children ?? (loading ? \"Authenticating...\" : \"Sign In with Solana\")}\n </button>\n );\n}\n\nexport const SolanaSignInButton: FC<SolanaSignInButtonProps> = ({\n cluster,\n endpoint,\n ...props\n}) => {\n return (\n <SolanaProvider cluster={cluster} endpoint={endpoint}>\n <SolanaSignInInner {...props} />\n </SolanaProvider>\n );\n};\n","import type { SolanaSignInInput, SolanaSignInOutput } from \"@solana/wallet-standard-features\";\n\nexport type { SolanaSignInInput, SolanaSignInOutput };\n\nexport interface SiwsPluginOptions {\n statement?: string;\n chainId?: string;\n nonceExpiryMs?: number;\n anonymous?: boolean;\n}\n\nexport interface SiwsNonceResponse {\n nonce: string;\n domain: string;\n uri: string;\n statement: string;\n chainId: string;\n issuedAt: string;\n}\n\nexport interface SiwsVerifyRequest {\n output: {\n account: {\n address: string;\n publicKey: number[];\n };\n signature: number[];\n signedMessage: number[];\n };\n input: {\n domain: string;\n address?: string;\n statement?: string;\n uri?: string;\n version?: string;\n chainId?: string;\n nonce?: string;\n issuedAt?: string;\n expirationTime?: string;\n notBefore?: string;\n requestId?: string;\n resources?: string[];\n };\n}\n\nexport interface SiwsVerifyResponse {\n success: boolean;\n user: {\n id: string;\n name: string;\n email: string;\n };\n}\n\nexport interface SiwsLinkRequest {\n input: SolanaSignInInput;\n output: {\n account: {\n address: string;\n publicKey: number[];\n };\n signature: number[];\n signedMessage: number[];\n };\n}\n\nexport interface SiwsLinkedWallet {\n walletAddress: string;\n createdAt: Date;\n}\n\nexport interface SerializedSolanaOutput {\n account: {\n address: string;\n publicKey: number[];\n };\n signature: number[];\n signedMessage: number[];\n signatureType?: string;\n}\n\nexport function serializeOutput(output: SolanaSignInOutput): SerializedSolanaOutput {\n return {\n account: {\n address: output.account.address,\n publicKey: Array.from(output.account.publicKey),\n },\n signature: Array.from(output.signature),\n signedMessage: Array.from(output.signedMessage),\n signatureType: (output as any).signatureType,\n };\n}\n\nexport function deserializeOutput(data: SerializedSolanaOutput): SolanaSignInOutput {\n return {\n account: {\n address: data.account.address,\n publicKey: new Uint8Array(data.account.publicKey),\n chains: [],\n features: [],\n },\n signature: new Uint8Array(data.signature),\n signedMessage: new Uint8Array(data.signedMessage),\n };\n}\n","\"use client\";\n\nimport { useEffect, useState, type FC } from \"react\";\nimport { useWallet } from \"@solana/wallet-adapter-react\";\nimport { useWalletModal } from \"@solana/wallet-adapter-react-ui\";\nimport type { SolanaSignInInput, SolanaSignInOutput } from \"@solana/wallet-standard-features\";\nimport { SolanaProvider, type SolanaProviderProps } from \"./provider\";\n\nexport type SolanaLinkWalletProps = {\n baseURL?: string;\n onLink?: (walletAddress: string) => void;\n onUnlink?: () => void;\n onError?: (error: string) => void;\n className?: string;\n unlinkClassName?: string;\n cluster?: SolanaProviderProps[\"cluster\"];\n endpoint?: SolanaProviderProps[\"endpoint\"];\n renderLinked?: (wallet: { address: string; onUnlink: () => void }) => React.ReactNode;\n children?: React.ReactNode;\n};\n\nfunction LinkWalletInner({\n baseURL = \"\",\n onLink,\n onUnlink,\n onError,\n className,\n unlinkClassName,\n renderLinked,\n children,\n}: Omit<SolanaLinkWalletProps, \"cluster\" | \"endpoint\">) {\n const { publicKey, connected, disconnect, wallet, connecting, connect } =\n useWallet();\n const { setVisible, visible } = useWalletModal();\n const [linkedWallet, setLinkedWallet] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [linking, setLinking] = useState(false);\n const [hasTriggered, setHasTriggered] = useState(false);\n\n const fetchWallets = async () => {\n try {\n const res = await fetch(`${baseURL}/api/auth/siws/wallets`);\n const data = await res.json();\n if (data.wallets?.[0]) {\n setLinkedWallet(data.wallets[0].walletAddress);\n }\n } catch {}\n setLoading(false);\n };\n\n useEffect(() => {\n fetchWallets();\n }, []);\n\n useEffect(() => {\n if (connected && publicKey && linking && !hasTriggered) {\n setHasTriggered(true);\n setVisible(false);\n handleLink();\n }\n }, [connected, publicKey, linking, wallet, hasTriggered]);\n\n useEffect(() => {\n if (wallet && !connected && !connecting && linking) {\n connect().catch(console.error);\n }\n }, [wallet, connected, connecting, linking, connect]);\n\n useEffect(() => {\n if (!visible && linking && !connected && !connecting && !wallet) {\n setLinking(false);\n setHasTriggered(false);\n }\n }, [visible, linking, connected, connecting, wallet]);\n\n const handleLinkClick = async () => {\n setLinking(true);\n if (wallet && !connected) {\n try {\n await connect();\n } catch {\n setVisible(true);\n }\n } else if (!wallet) {\n setVisible(true);\n }\n };\n\n const handleLink = async () => {\n if (!wallet?.adapter) return;\n\n const adapter = wallet.adapter as any;\n if (!(\"signIn\" in adapter)) {\n onError?.(\"Wallet doesn't support SIWS\");\n setLinking(false);\n disconnect();\n return;\n }\n\n try {\n const createRes = await fetch(`${baseURL}/api/auth/siws/nonce`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n walletAddress: publicKey!.toBase58(),\n }),\n });\n const nonceData = await createRes.json();\n\n const input: SolanaSignInInput = {\n domain: nonceData.domain,\n statement: nonceData.statement,\n uri: nonceData.uri,\n nonce: nonceData.nonce,\n chainId: nonceData.chainId,\n issuedAt: nonceData.issuedAt,\n };\n\n const output: SolanaSignInOutput = await adapter.signIn(input);\n\n const serializedOutput = {\n account: {\n address: output.account.address,\n publicKey: Array.from(output.account.publicKey),\n },\n signature: Array.from(output.signature),\n signedMessage: Array.from(output.signedMessage),\n };\n\n const linkRes = await fetch(`${baseURL}/api/auth/siws/link`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ input, output: serializedOutput }),\n });\n\n if (linkRes.ok) {\n const data = await linkRes.json();\n setLinkedWallet(data.walletAddress);\n onLink?.(data.walletAddress);\n } else {\n const err = await linkRes.json();\n onError?.(err.message || \"Failed to link wallet\");\n }\n } catch (e: any) {\n if (!e?.message?.includes(\"rejected\")) {\n onError?.(\"Failed to link wallet\");\n }\n } finally {\n setLinking(false);\n setHasTriggered(false);\n disconnect();\n }\n };\n\n const handleUnlink = async () => {\n if (!linkedWallet) return;\n try {\n const res = await fetch(`${baseURL}/api/auth/siws/unlink`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ walletAddress: linkedWallet }),\n });\n if (res.ok) {\n setLinkedWallet(null);\n onUnlink?.();\n }\n } catch {\n onError?.(\"Failed to unlink wallet\");\n }\n };\n\n if (loading) {\n return (\n <button disabled className={className}>\n Loading...\n </button>\n );\n }\n\n if (linkedWallet) {\n if (renderLinked) {\n return <>{renderLinked({ address: linkedWallet, onUnlink: handleUnlink })}</>;\n }\n const truncated = `${linkedWallet.slice(0, 4)}...${linkedWallet.slice(-4)}`;\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.5rem\" }}>\n <span style={{ fontFamily: \"monospace\", fontSize: \"0.875rem\" }}>\n {truncated}\n </span>\n <button onClick={handleUnlink} className={unlinkClassName} type=\"button\">\n Unlink\n </button>\n </div>\n );\n }\n\n return (\n <button\n onClick={handleLinkClick}\n disabled={linking}\n className={className}\n type=\"button\"\n >\n {children ?? (linking ? \"Linking...\" : \"Link Solana Wallet\")}\n </button>\n );\n}\n\nexport const SolanaLinkWallet: FC<SolanaLinkWalletProps> = ({\n cluster,\n endpoint,\n ...props\n}) => {\n return (\n <SolanaProvider cluster={cluster} endpoint={endpoint}>\n <LinkWalletInner {...props} />\n </SolanaProvider>\n );\n};\n"],"mappings":"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","names":["jsx","useEffect","useState","useWallet","useWalletModal","jsx","useWallet","useWalletModal","useState","useEffect"]}
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@pinklemon8/better-auth-siws",
3
+ "version": "0.1.0",
4
+ "description": "Sign In With Solana (SIWS) plugin for Better Auth — wallet authentication, linking, and ready-made React components",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ },
16
+ "./client": {
17
+ "types": "./dist/client.d.ts",
18
+ "import": "./dist/client.js",
19
+ "require": "./dist/client.cjs"
20
+ },
21
+ "./link": {
22
+ "types": "./dist/link.d.ts",
23
+ "import": "./dist/link.js",
24
+ "require": "./dist/link.cjs"
25
+ },
26
+ "./react": {
27
+ "types": "./dist/react/index.d.ts",
28
+ "import": "./dist/react/index.js",
29
+ "require": "./dist/react/index.cjs"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "dev": "tsup --watch",
40
+ "typecheck": "tsc --noEmit",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
+ "peerDependencies": {
44
+ "better-auth": ">=1.2.0",
45
+ "react": ">=18.0.0",
46
+ "react-dom": ">=18.0.0",
47
+ "@solana/wallet-adapter-react": ">=0.15.0",
48
+ "@solana/wallet-adapter-react-ui": ">=0.9.0",
49
+ "@solana/wallet-standard-features": ">=1.0.0",
50
+ "@solana/wallet-standard-util": ">=1.0.0",
51
+ "@solana/web3.js": ">=1.90.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "react": { "optional": true },
55
+ "react-dom": { "optional": true },
56
+ "@solana/wallet-adapter-react": { "optional": true },
57
+ "@solana/wallet-adapter-react-ui": { "optional": true }
58
+ },
59
+ "devDependencies": {
60
+ "better-auth": "^1.6.0",
61
+ "react": "^19.0.0",
62
+ "react-dom": "^19.0.0",
63
+ "@solana/wallet-adapter-react": "^0.15.39",
64
+ "@solana/wallet-adapter-react-ui": "^0.9.39",
65
+ "@solana/wallet-standard-features": "^1.3.0",
66
+ "@solana/wallet-standard-util": "^1.1.2",
67
+ "@solana/web3.js": "^1.98.4",
68
+ "@types/react": "^19.0.0",
69
+ "tsup": "^8.4.0",
70
+ "typescript": "^5.7.0"
71
+ },
72
+ "keywords": [
73
+ "better-auth",
74
+ "solana",
75
+ "siws",
76
+ "sign-in-with-solana",
77
+ "wallet",
78
+ "authentication",
79
+ "web3",
80
+ "phantom"
81
+ ],
82
+ "repository": {
83
+ "type": "git",
84
+ "url": "https://github.com/AYousef/better-auth-siws"
85
+ },
86
+ "homepage": "https://github.com/AYousef/better-auth-siws#readme"
87
+ }