better-near-auth 0.3.3 → 0.5.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.
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  </div>
15
15
 
16
- This [Better Auth](https://better-auth.com) plugin enables secure authentication via NEAR wallets and keypairs by following the [NEP-413 standard](https://github.com/near/NEPs/blob/master/neps/nep-0413.md). It leverages [near-sign-verify](https://github.com/elliotBraem/near-sign-verify), [near-kit](https://kit.near.tools/), and [Hot Connect](https://github.com/azbang/hot-connector) to provide a complete drop-in solution with session management, secure defaults, and automatic profile integration.
16
+ This [Better Auth](https://better-auth.com) plugin enables secure authentication via NEAR wallets and keypairs by following the [NEP-413 standard](https://github.com/near/NEPs/blob/master/neps/nep-0413.md). It leverages [near-sign-verify](https://github.com/elliotBraem/near-sign-verify), [near-kit](https://kit.near.tools/), and [NEAR Connect](https://github.com/azbang/near-connect) to provide a complete drop-in solution with session management, secure defaults, and automatic profile integration.
17
17
 
18
18
  ## Installation
19
19
 
@@ -57,7 +57,7 @@ npm install better-near-auth
57
57
  export const authClient = createAuthClient({
58
58
  plugins: [
59
59
  siwnClient({
60
- domain: "myapp.com", // this doesn't actually do anything yet... taking suggestions
60
+ recipient: "myapp.com",
61
61
  networkId: "mainnet", // optional, default is "mainnet"
62
62
  })
63
63
  ],
@@ -66,42 +66,9 @@ npm install better-near-auth
66
66
 
67
67
  ## Usage
68
68
 
69
- ### Two-Step Authentication Flow
70
-
71
- The plugin uses a secure two-step authentication process:
72
-
73
- 1. **Step 1**: Connect wallet and cache nonce
74
- 2. **Step 2**: Sign message and authenticate
75
-
76
- ```ts title="two-step-auth.ts"
77
- // Step 1: Connect wallet and get nonce
78
- await authClient.requestSignIn.near(
79
- { recipient: "myapp.com" },
80
- {
81
- onSuccess: () => {
82
- console.log("Wallet connected, nonce cached!");
83
- },
84
- onError: (error) => {
85
- console.error("Wallet connection failed:", error.message);
86
- }
87
- }
88
- );
89
-
90
- // Step 2: Sign message and authenticate
91
- await authClient.signIn.near(
92
- { recipient: "myapp.com" },
93
- {
94
- onSuccess: () => {
95
- console.log("Successfully signed in!");
96
- },
97
- onError: (error) => {
98
- console.error("Sign in failed:", error.message);
99
- }
100
- }
101
- );
102
- ```
69
+ ### Single-Step Authentication Flow
103
70
 
104
- ### Complete React Component Example
71
+ The plugin uses a single-step authentication flow that automatically handles both wallet connection and message signing:
105
72
 
106
73
  ```tsx title="LoginButton.tsx"
107
74
  import { authClient } from "./auth-client";
@@ -109,56 +76,71 @@ import { useState } from "react";
109
76
 
110
77
  export function LoginButton() {
111
78
  const { data: session } = authClient.useSession();
112
- const [isConnectingWallet, setIsConnectingWallet] = useState(false);
113
79
  const [isSigningIn, setIsSigningIn] = useState(false);
114
-
115
- // Get account ID from near-kit client
116
- const accountId = authClient.near.getAccountId();
117
80
 
118
81
  if (session) {
119
82
  return (
120
83
  <div>
121
84
  <p>Welcome, {session.user.name}!</p>
122
- <button onClick={() => authClient.signOut()}>Sign out</button>
85
+ <button onClick={() => authClient.near.disconnect()}>Sign out</button>
123
86
  </div>
124
87
  );
125
88
  }
126
89
 
127
- const handleWalletConnect = async () => {
128
- setIsConnectingWallet(true);
129
-
130
- try {
131
- await authClient.requestSignIn.near(
132
- { recipient: "myapp.com" },
133
- {
134
- onSuccess: () => {
135
- setIsConnectingWallet(false);
136
- console.log("Wallet connected!");
137
- },
138
- onError: (error) => {
139
- setIsConnectingWallet(false);
140
- console.error("Wallet connection failed:", error.message);
141
- },
142
- }
143
- );
144
- } catch (error) {
145
- setIsConnectingWallet(false);
146
- console.error("Authentication error:", error);
147
- }
148
- };
149
-
150
90
  const handleSignIn = async () => {
151
91
  setIsSigningIn(true);
152
92
 
153
- try {
154
- await authClient.signIn.near(
155
- { recipient: "myapp.com" },
156
- {
157
- onSuccess: () => {
158
- setIsSigningIn(false);
159
- console.log("Successfully signed in!");
160
- },
161
- onError: (error) => {
93
+ await authClient.signIn.near({
94
+ onSuccess: () => {
95
+ setIsSigningIn(false);
96
+ console.log("Successfully signed in!");
97
+ },
98
+ onError: (error) => {
99
+ setIsSigningIn(false);
100
+ console.error("Sign in failed:", error.message);
101
+ },
102
+ });
103
+ };
104
+
105
+ return (
106
+ <button onClick={handleSignIn} disabled={isSigningIn}>
107
+ {isSigningIn ? "Signing in..." : "Sign in with NEAR"}
108
+ </button>
109
+ );
110
+ }
111
+ ```
112
+
113
+ ### How It Works
114
+
115
+ The `signIn.near()` method automatically:
116
+
117
+ 1. **Checks wallet capabilities** - Detects if the wallet supports `signInAndSignMessage`
118
+ 2. **Single-step flow** (supported wallets): One popup for connection + signing
119
+ 3. **Two-step fallback** (unsupported wallets): Automatic fallback to connect then sign
120
+
121
+ **Supported wallets for single-step:**
122
+ - Meteor Wallet
123
+ - Intear Wallet
124
+ - NEAR CLI
125
+ - HOT Wallet
126
+ - MyNearWallet
127
+ - And more...
128
+
129
+ ### Manual Two-Step Flow (Optional)
130
+
131
+ If you need explicit control over the connection step:
132
+
133
+ ```ts
134
+ // Step 1: Connect wallet (optional, for explicit control)
135
+ await authClient.requestSignIn.near({
136
+ onSuccess: () => console.log("Wallet connected"),
137
+ });
138
+
139
+ // Step 2: Sign and authenticate
140
+ await authClient.signIn.near({
141
+ onSuccess: () => console.log("Signed in!"),
142
+ });
143
+ ```
162
144
  setIsSigningIn(false);
163
145
  console.error("Sign in failed:", error.message);
164
146
  },
@@ -172,15 +154,9 @@ export function LoginButton() {
172
154
 
173
155
  return (
174
156
  <div>
175
- {!accountId ? (
176
- <button onClick={handleWalletConnect} disabled={isConnectingWallet}>
177
- {isConnectingWallet ? "Connecting..." : "Connect NEAR Wallet"}
178
- </button>
179
- ) : (
180
- <button onClick={handleSignIn} disabled={isSigningIn}>
181
- {isSigningIn ? "Signing in..." : `Sign in with NEAR (${accountId})`}
182
- </button>
183
- )}
157
+ <button onClick={handleSignIn} disabled={isSigningIn}>
158
+ {isSigningIn ? "Signing in..." : "Sign in with NEAR"}
159
+ </button>
184
160
  </div>
185
161
  );
186
162
  }
@@ -235,7 +211,7 @@ The SIWN plugin accepts the following configuration options:
235
211
 
236
212
  The SIWN client plugin accepts the following configuration options:
237
213
 
238
- * **domain**: Domain identifier... idk what it should do yet. Maybe shade agent.
214
+ * **recipient**: The recipient identifier for NEP-413 messages (must match server config)
239
215
  * **networkId**: NEAR network to use ("mainnet" or "testnet"). Default is "mainnet"
240
216
 
241
217
  ```ts title="auth-client.ts"
@@ -245,7 +221,7 @@ import { siwnClient } from "better-near-auth/client";
245
221
  export const authClient = createAuthClient({
246
222
  plugins: [
247
223
  siwnClient({
248
- domain: "myapp.com",
224
+ recipient: "myapp.com",
249
225
  networkId: "testnet", // Use testnet
250
226
  }),
251
227
  ],
@@ -280,14 +256,17 @@ The client plugin provides the following actions:
280
256
  - `getNearClient()` - Get the near-kit client instance
281
257
  - `getAccountId()` - Get the currently connected account ID
282
258
  - `disconnect()` - Disconnect wallet and clear cached data
259
+ - `link(callbacks?)` - Link a NEAR account to the current session
260
+ - `unlink(params)` - Unlink a NEAR account from the current session
261
+ - `listAccounts()` - List all linked NEAR accounts
283
262
 
284
263
  #### `authClient.requestSignIn`
285
264
 
286
- - `near(params, callbacks?)` - Connect wallet and cache nonce (Step 1)
265
+ - `near(callbacks?)` - Connect wallet and cache nonce (for two-step flow)
287
266
 
288
267
  #### `authClient.signIn`
289
268
 
290
- - `near(params, callbacks?)` - Sign message and authenticate (Step 2)
269
+ - `near(callbacks?)` - Sign message and authenticate (single-step or two-step)
291
270
 
292
271
  ### Callback Interface
293
272
 
@@ -303,10 +282,10 @@ interface AuthCallbacks {
303
282
  Common error codes you may encounter:
304
283
 
305
284
  - `SIGNER_NOT_AVAILABLE` - NEAR wallet not available
306
- - `WALLET_NOT_CONNECTED` - Wallet not connected before signing
307
- - `NONCE_NOT_FOUND` - No valid cached nonce found
308
- - `ACCOUNT_MISMATCH` - Cached nonce doesn't match current account
309
- - `UNAUTHORIZED_INVALID_OR_EXPIRED_NONCE` - Server nonce expired or invalid
285
+ - `WALLET_NOT_CONNECTED` - Wallet not connected before signing (two-step fallback)
286
+ - `ACCOUNT_MISMATCH` - Cached nonce doesn't match current account (two-step fallback)
287
+ - `UNAUTHORIZED_NONCE_REPLAY` - Nonce already used (replay attack detected)
288
+ - `UNAUTHORIZED_INVALID_SIGNATURE` - Invalid signature verification
310
289
 
311
290
  ## Advanced Configuration
312
291
 
@@ -446,6 +425,67 @@ export const authClient = createAuthClient({
446
425
  - Testnet accounts must use "testnet", mainnet accounts use "mainnet"
447
426
 
448
427
 
428
+ ## Examples
429
+
430
+ This repository includes example applications demonstrating how to use better-near-auth:
431
+
432
+ ### Browser to Server Example
433
+
434
+ A full-stack example showing NEAR authentication in a browser app with a server backend.
435
+
436
+ - **Location**: `examples/browser-2-server/`
437
+ - **Live Demo**: [better-near-auth.near.page](https://better-near-auth.near.page)
438
+ - **Tech Stack**: Hono, Drizzle ORM, React, TanStack Router
439
+
440
+ **Running locally:**
441
+ ```bash
442
+ # From repo root
443
+ pnpm install
444
+ cd examples/browser-2-server
445
+ pnpm dev
446
+ ```
447
+
448
+ **Deployment:**
449
+ Each example can be deployed independently to Railway or other platforms. See the example's README for deployment instructions.
450
+
451
+ ## Development
452
+
453
+ Interested in contributing? See [CONTRIBUTING.md](./CONTRIBUTING.md) for:
454
+ - Development setup
455
+ - How to add changesets
456
+ - Pull request guidelines
457
+ - Release process
458
+
459
+ **Quick start:**
460
+ ```bash
461
+ # Install dependencies
462
+ pnpm install
463
+
464
+ # Build the package
465
+ pnpm build
466
+
467
+ # Run type checking
468
+ pnpm typecheck
469
+
470
+ # Run tests
471
+ pnpm test
472
+
473
+ # Run example locally
474
+ cd examples/browser-2-server && pnpm dev
475
+ ```
476
+
477
+ **Build output:**
478
+ - `dist/index.js` - Server plugin (ESM)
479
+ - `dist/client.js` - Client plugin (ESM)
480
+ - `dist/*.d.ts` - TypeScript declarations
481
+
482
+ **Deployment workflow:**
483
+ 1. Make changes and create changeset
484
+ 2. Merge PR → Changesets publishes to npm
485
+ 3. Example auto-updates to use published version
486
+ 4. Railway auto-deploys
487
+ 5. Example reverts to `workspace:*` for next development cycle
488
+
449
489
  ## Links
450
490
 
451
491
  * [Better Auth Documentation](https://better-auth.com)
@@ -453,5 +493,6 @@ export const authClient = createAuthClient({
453
493
  * [NEP-413 Specification](https://github.com/near/NEPs/blob/master/neps/nep-0413.md)
454
494
  * [near-sign-verify](https://github.com/elliotBraem/near-sign-verify)
455
495
  * [near-kit](https://kit.near.tools/)
456
- * [Hot Connect](https://github.com/azbang/hot-connector)
457
- * [Example Implementation](https://better-near-auth.near.page)
496
+ * [NEAR Connect](https://github.com/azbang/near-connect)
497
+ * [Example Implementation](https://better-near-auth.near.page) - Live demo
498
+ * [Contributing Guide](./CONTRIBUTING.md) - Development and contribution guidelines
@@ -0,0 +1,70 @@
1
+ import type { BetterAuthClientPlugin, BetterFetch, BetterFetchResponse } from "better-auth/client";
2
+ import { atom } from "nanostores";
3
+ import { Near } from "near-kit";
4
+ import type { siwn } from "./index.js";
5
+ import { type AccountId, type NonceRequestT, type NonceResponseT, type ProfileResponseT, type VerifyRequestT, type VerifyResponseT } from "./types.js";
6
+ export interface AuthCallbacks {
7
+ onSuccess?: () => void;
8
+ onError?: (error: Error & {
9
+ status?: number;
10
+ code?: string;
11
+ }) => void;
12
+ }
13
+ export interface SIWNClientConfig {
14
+ recipient: string;
15
+ networkId?: "mainnet" | "testnet";
16
+ }
17
+ export interface CachedNonceData {
18
+ nonce: string;
19
+ accountId: string;
20
+ publicKey?: string | null;
21
+ networkId: string;
22
+ timestamp: number;
23
+ }
24
+ export interface SIWNClientActions {
25
+ near: {
26
+ nonce: (params: NonceRequestT) => Promise<BetterFetchResponse<NonceResponseT>>;
27
+ verify: (params: VerifyRequestT) => Promise<BetterFetchResponse<VerifyResponseT>>;
28
+ getProfile: (accountId?: AccountId) => Promise<BetterFetchResponse<ProfileResponseT>>;
29
+ getNearClient: () => Near;
30
+ getAccountId: () => string | null;
31
+ getState: () => {
32
+ accountId: string | null;
33
+ publicKey: string | null;
34
+ networkId: string;
35
+ } | null;
36
+ disconnect: () => Promise<void>;
37
+ link: (callbacks?: AuthCallbacks) => Promise<void>;
38
+ unlink: (params: {
39
+ accountId: string;
40
+ network?: "mainnet" | "testnet";
41
+ }) => Promise<BetterFetchResponse<{
42
+ success: boolean;
43
+ message: string;
44
+ }>>;
45
+ listAccounts: () => Promise<BetterFetchResponse<{
46
+ accounts: any[];
47
+ }>>;
48
+ };
49
+ requestSignIn: {
50
+ near: (callbacks?: AuthCallbacks) => Promise<void>;
51
+ };
52
+ signIn: {
53
+ near: (callbacks?: AuthCallbacks) => Promise<void>;
54
+ };
55
+ }
56
+ export interface SIWNClientPlugin extends BetterAuthClientPlugin {
57
+ id: "siwn";
58
+ $InferServerPlugin: ReturnType<typeof siwn>;
59
+ getAtoms: ($fetch: BetterFetch) => {
60
+ nearState: ReturnType<typeof atom<{
61
+ accountId: string | null;
62
+ publicKey: string | null;
63
+ networkId: string;
64
+ } | null>>;
65
+ cachedNonce: ReturnType<typeof atom<CachedNonceData | null>>;
66
+ };
67
+ getActions: ($fetch: BetterFetch) => SIWNClientActions;
68
+ }
69
+ export declare const siwnClient: (config: SIWNClientConfig) => SIWNClientPlugin;
70
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}