better-near-auth 0.5.2 → 0.6.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
@@ -8,12 +8,19 @@
8
8
  <h1 style="font-size: 2.5rem; font-weight: bold;">better-near-auth</h1>
9
9
 
10
10
  <p>
11
- <strong>Sign in with NEAR (SIWN) plugin for better-auth</strong>
11
+ <strong>Sign in with NEAR + gasless relay plugin for Better Auth</strong>
12
12
  </p>
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 [NEAR Connect](https://github.com/azbang/near-connect) 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 following [NEP-413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md) and adds a built-in [NEP-366](https://github.com/near/NEPs/blob/master/neps/nep-0366.md) delegate action relayer so authenticated users can call on-chain contracts gaslessly. It uses [FastNear](https://fastnear.com) for wallet connection, RPC queries, and transaction broadcasting.
17
+
18
+ ## Features
19
+
20
+ - **SIWN authentication** — wallet-based sign-in with automatic single-step/two-step flow detection
21
+ - **Gasless relay** — server relays signed delegate actions on-chain, paying gas from a relayer account
22
+ - **Ephemeral relayer keypair** — auto-generated ED25519 keypair on first startup, private key encrypted with AES-256-GCM in the database, persists across restarts
23
+ - **Profile integration** — FastNear KV primary, NEAR Social fallback
17
24
 
18
25
  ## Installation
19
26
 
@@ -36,7 +43,12 @@ npm install better-near-auth
36
43
  plugins: [
37
44
  siwn({
38
45
  recipient: "myapp.com",
39
- anonymous: true, // optional, default is true
46
+ anonymous: true,
47
+
48
+ // Optional: enable gasless relay
49
+ relayer: {
50
+ whitelistedContracts: ["myapp.near"],
51
+ },
40
52
  }),
41
53
  ],
42
54
  });
@@ -58,7 +70,7 @@ npm install better-near-auth
58
70
  plugins: [
59
71
  siwnClient({
60
72
  recipient: "myapp.com",
61
- networkId: "mainnet", // optional, default is "mainnet"
73
+ networkId: "mainnet",
62
74
  })
63
75
  ],
64
76
  });
@@ -66,9 +78,9 @@ npm install better-near-auth
66
78
 
67
79
  ## Usage
68
80
 
69
- ### Single-Step Authentication Flow
81
+ ### Sign In with NEAR
70
82
 
71
- The plugin uses a single-step authentication flow that automatically handles both wallet connection and message signing:
83
+ The `signIn.near()` method automatically detects wallet capabilities and uses the best available flow:
72
84
 
73
85
  ```tsx title="LoginButton.tsx"
74
86
  import { authClient } from "./auth-client";
@@ -89,11 +101,9 @@ export function LoginButton() {
89
101
 
90
102
  const handleSignIn = async () => {
91
103
  setIsSigningIn(true);
92
-
93
104
  await authClient.signIn.near({
94
105
  onSuccess: () => {
95
106
  setIsSigningIn(false);
96
- console.log("Successfully signed in!");
97
107
  },
98
108
  onError: (error) => {
99
109
  setIsSigningIn(false);
@@ -110,83 +120,59 @@ export function LoginButton() {
110
120
  }
111
121
  ```
112
122
 
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...
123
+ **Supported wallets for single-step flow:** Meteor Wallet, Intear Wallet, NEAR CLI, HOT Wallet, MyNearWallet, and more.
128
124
 
129
125
  ### Manual Two-Step Flow (Optional)
130
126
 
131
- If you need explicit control over the connection step:
132
-
133
127
  ```ts
134
- // Step 1: Connect wallet (optional, for explicit control)
135
128
  await authClient.requestSignIn.near({
136
129
  onSuccess: () => console.log("Wallet connected"),
137
130
  });
138
131
 
139
- // Step 2: Sign and authenticate
140
132
  await authClient.signIn.near({
141
133
  onSuccess: () => console.log("Signed in!"),
142
134
  });
143
135
  ```
144
- setIsSigningIn(false);
145
- console.error("Sign in failed:", error.message);
146
- },
147
- }
148
- );
149
- } catch (error) {
150
- setIsSigningIn(false);
151
- console.error("Authentication error:", error);
152
- }
153
- };
154
136
 
155
- return (
156
- <div>
157
- <button onClick={handleSignIn} disabled={isSigningIn}>
158
- {isSigningIn ? "Signing in..." : "Sign in with NEAR"}
159
- </button>
160
- </div>
161
- );
162
- }
137
+ ### Gasless Relay
138
+
139
+ Once the relayer is configured on the server, authenticated users can call on-chain contracts without paying gas:
140
+
141
+ ```ts
142
+ // 1. Build a signed delegate action using the wallet's FAK
143
+ const signedAction = await authClient.near.buildSignedDelegateAction({
144
+ receiverId: "myapp.near",
145
+ actions: [{
146
+ type: "FunctionCall",
147
+ methodName: "some_method",
148
+ args: new TextEncoder().encode(JSON.stringify({ key: "value" })),
149
+ gas: "30000000000000",
150
+ deposit: "0",
151
+ }],
152
+ });
153
+
154
+ // 2. Relay it — the server pays gas
155
+ const result = await authClient.near.relayTransaction({
156
+ signedDelegateAction: signedAction,
157
+ });
158
+
159
+ console.log("Tx hash:", result.txHash);
160
+
161
+ // 3. Check status
162
+ const status = await authClient.near.getRelayStatus(result.txHash);
163
163
  ```
164
164
 
165
165
  ### Profile Access
166
166
 
167
- Access user profiles from NEAR Social automatically:
168
-
169
- ```ts title="profile-usage.ts"
170
- // Get current user's profile (requires authentication)
167
+ ```ts
171
168
  const myProfile = await authClient.near.getProfile();
172
- console.log("My profile:", myProfile);
173
-
174
- // Get specific user's profile (no auth required)
175
169
  const aliceProfile = await authClient.near.getProfile("alice.near");
176
- console.log("Alice's profile:", aliceProfile);
177
170
  ```
178
171
 
179
172
  ### Wallet Management
180
173
 
181
- ```ts title="wallet-management.ts"
182
- // Check if wallet is connected
174
+ ```ts
183
175
  const accountId = authClient.near.getAccountId();
184
- console.log("Connected account:", accountId);
185
-
186
- // Get the embedded NEAR client
187
- const nearClient = authClient.near.getNearClient();
188
-
189
- // Disconnect wallet and clear cached data
190
176
  await authClient.near.disconnect();
191
177
  ```
192
178
 
@@ -194,79 +180,105 @@ await authClient.near.disconnect();
194
180
 
195
181
  ### Server Options
196
182
 
197
- The SIWN plugin accepts the following configuration options:
198
-
199
- * **recipient**: The recipient identifier for NEP-413 messages (required)
200
- * **anonymous**: Whether to allow anonymous sign-ins without requiring an email. Default is `true`
201
- * **emailDomainName**: The email domain name for creating user accounts when not using anonymous mode. Defaults to the recipient value
202
- * **requireFullAccessKey**: Whether to require full access keys. Default is `true`
203
- * **getNonce**: Function to generate a unique nonce for each sign-in attempt. Optional, uses secure defaults
204
- * **validateNonce**: Function to validate nonces. Optional, uses time-based validation by default
205
- * **validateRecipient**: Function to validate recipients. Optional, uses exact match by default
206
- * **validateMessage**: Function to validate messages. Optional, no validation by default
207
- * **getProfile**: Function to fetch user profiles. Optional, uses NEAR Social by default
208
- * **validateLimitedAccessKey**: Function to validate function call access keys when `requireFullAccessKey` is false
183
+ | Option | Type | Default | Description |
184
+ |---|---|---|---|
185
+ | `recipient` | `string` | | NEP-413 recipient identifier (required) |
186
+ | `anonymous` | `boolean` | `true` | Allow anonymous sign-ins |
187
+ | `emailDomainName` | `string` | recipient | Email domain for non-anonymous accounts |
188
+ | `requireFullAccessKey` | `boolean` | `true` | Require full access keys |
189
+ | `getNonce` | `() => Promise<Uint8Array>` | | Custom nonce generation |
190
+ | `validateNonce` | `(nonce: Uint8Array) => boolean` | | Custom nonce validation |
191
+ | `validateRecipient` | `(recipient: string) => boolean` | | Custom recipient validation |
192
+ | `validateMessage` | `(message: string) => boolean` | — | Custom message validation |
193
+ | `getProfile` | `(accountId: string) => Promise<Profile \| null>` | | Custom profile lookup |
194
+ | `validateLimitedAccessKey` | `(args) => Promise<boolean>` | | Validate FAK when `requireFullAccessKey` is false |
195
+ | `fastnearApiKey` | `string` | `process.env.FASTNEAR_API_KEY` | FastNear API key |
196
+ | `relayer` | `RelayerConfig` | — | Relayer configuration (see below) |
197
+
198
+ #### Relayer Configuration
199
+
200
+ | Option | Type | Default | Description |
201
+ |---|---|---|---|
202
+ | `accountId` | `string` | — | Named relayer account (explicit mode) |
203
+ | `privateKey` | `string` | — | Base64 private key (explicit mode) |
204
+ | `relayTarget` | `string` | FastNear RPC | RPC URL for broadcasting |
205
+ | `whitelistedContracts` | `string[]` | — | Restrict relay to these contracts |
206
+ | `maxGasPerTransaction` | `string` | `"300 Tgas"` | Max gas per relayed tx |
207
+ | `maxDepositPerTransaction` | `string` | `"0"` | Max deposit per relayed tx |
208
+
209
+ When `accountId` and `privateKey` are omitted, the relayer starts in **ephemeral mode**: an ED25519 keypair is generated on first startup, the implicit account ID is derived from the public key, and the private key is encrypted with AES-256-GCM (using `BETTER_AUTH_SECRET` as KEK via HKDF-SHA256) and stored in the database. The same keypair is recovered on restart.
209
210
 
210
211
  ### Client Options
211
212
 
212
- The SIWN client plugin accepts the following configuration options:
213
-
214
- * **recipient**: The recipient identifier for NEP-413 messages (must match server config)
215
- * **networkId**: NEAR network to use ("mainnet" or "testnet"). Default is "mainnet"
216
-
217
- ```ts title="auth-client.ts"
218
- import { createAuthClient } from "better-auth/client";
219
- import { siwnClient } from "better-near-auth/client";
220
-
221
- export const authClient = createAuthClient({
222
- plugins: [
223
- siwnClient({
224
- recipient: "myapp.com",
225
- networkId: "testnet", // Use testnet
226
- }),
227
- ],
228
- });
229
- ```
213
+ | Option | Type | Default | Description |
214
+ |---|---|---|---|
215
+ | `recipient` | `string` | | NEP-413 recipient (must match server) |
216
+ | `networkId` | `string` | `"mainnet"` | NEAR network |
230
217
 
231
218
  ## Schema
232
219
 
233
- The SIWN plugin adds a `nearAccount` table to store user NEAR account associations:
234
-
235
- | Field | Type | Description |
236
- | --------- | ------- | ----------------------------------------- |
237
- | id | string | Primary key |
238
- | userId | string | Reference to user.id |
239
- | accountId | string | NEAR account ID |
240
- | network | string | Network (mainnet or testnet) |
241
- | publicKey | string | Associated public key |
242
- | isPrimary | boolean | Whether this is the user's primary account|
243
- | createdAt | date | Creation timestamp |
220
+ ### nearAccount
221
+
222
+ | Field | Type | Description |
223
+ |---|---|---|
224
+ | id | string | Primary key |
225
+ | userId | string | user.id |
226
+ | accountId | string | NEAR account ID |
227
+ | network | string | mainnet/testnet |
228
+ | publicKey | string | Associated public key |
229
+ | isPrimary | boolean | User's primary account |
230
+ | createdAt | date | |
231
+
232
+ ### relayedTransaction
233
+
234
+ | Field | Type | Description |
235
+ |---|---|---|
236
+ | userId | string | → user.id |
237
+ | txHash | string | On-chain tx hash |
238
+ | senderId | string | Delegate action sender |
239
+ | receiverId | string | Contract called |
240
+ | status | string | pending/completed/failed |
241
+ | gasUsed | string | Gas consumed |
242
+ | createdAt | date | |
243
+
244
+ ### relayerKey
245
+
246
+ | Field | Type | Description |
247
+ |---|---|---|
248
+ | id | string | Singleton per network |
249
+ | accountId | string | Implicit NEAR account ID |
250
+ | encryptedPrivateKey | string | AES-256-GCM encrypted, base64 |
251
+ | iv | string | Initialization vector, base64 |
252
+ | publicKey | string | ed25519:base64 format |
253
+ | network | string | mainnet/testnet |
254
+ | createdAt | date | |
255
+ | lastUsedAt | date | Updated on each relay |
244
256
 
245
257
  ## API Reference
246
258
 
247
- ### Client Actions
259
+ ### Client Actions — `authClient.near`
248
260
 
249
- The client plugin provides the following actions:
261
+ **SIWN**
262
+ - `nonce(params)` — Request a nonce from the server
263
+ - `verify(params)` — Verify an auth token with the server
264
+ - `getProfile(accountId?)` — Get user profile (FastNear KV → NEAR Social fallback)
265
+ - `getAccountId()` — Currently connected account ID
266
+ - `getState()` — Current wallet state
267
+ - `disconnect()` — Disconnect wallet and clear cached data
268
+ - `link(callbacks?)` — Link a NEAR account to the current session
269
+ - `unlink(params)` — Unlink a NEAR account
270
+ - `listAccounts()` — List all linked NEAR accounts
250
271
 
251
- #### `authClient.near`
272
+ **Relay**
273
+ - `buildSignedDelegateAction({ receiverId, actions })` — Build + sign a delegate action via wallet FAK
274
+ - `relayTransaction({ signedDelegateAction })` — Submit a signed delegate action to the relayer
275
+ - `getRelayStatus(txHash)` — Check relayed transaction status
252
276
 
253
- - `nonce(params)` - Request a nonce from the server
254
- - `verify(params)` - Verify an auth token with the server
255
- - `getProfile(accountId?)` - Get user profile from NEAR Social
256
- - `getNearClient()` - Get the near-kit client instance
257
- - `getAccountId()` - Get the currently connected account ID
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
277
+ ### `authClient.requestSignIn`
278
+ - `near(callbacks?)` Connect wallet and cache nonce (two-step flow)
262
279
 
263
- #### `authClient.requestSignIn`
264
-
265
- - `near(callbacks?)` - Connect wallet and cache nonce (for two-step flow)
266
-
267
- #### `authClient.signIn`
268
-
269
- - `near(callbacks?)` - Sign message and authenticate (single-step or two-step)
280
+ ### `authClient.signIn`
281
+ - `near(callbacks?)` — Sign message and authenticate (single-step or two-step)
270
282
 
271
283
  ### Callback Interface
272
284
 
@@ -279,18 +291,30 @@ interface AuthCallbacks {
279
291
 
280
292
  ### Error Codes
281
293
 
282
- Common error codes you may encounter:
283
-
284
- - `SIGNER_NOT_AVAILABLE` - NEAR wallet not available
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
294
+ | Code | Description |
295
+ |---|---|
296
+ | `SIGNER_NOT_AVAILABLE` | NEAR wallet not available |
297
+ | `WALLET_NOT_CONNECTED` | Wallet not connected before signing |
298
+ | `ACCOUNT_MISMATCH` | Cached nonce doesn't match current account |
299
+ | `UNAUTHORIZED_NONCE_REPLAY` | Nonce already used |
300
+ | `UNAUTHORIZED_INVALID_SIGNATURE` | Invalid signature verification |
301
+
302
+ ### Server Endpoints
303
+
304
+ | Method | Path | Description |
305
+ |---|---|---|
306
+ | POST | `/near/nonce` | Generate nonce for signing |
307
+ | POST | `/near/verify` | Verify NEP-413 signature, create session |
308
+ | POST | `/near/profile` | Get NEAR profile |
309
+ | POST | `/near/link-account` | Link NEAR account to session |
310
+ | POST | `/near/unlink-account` | Unlink NEAR account |
311
+ | GET | `/near/list-accounts` | List linked NEAR accounts |
312
+ | POST | `/near/relay` | Relay a signed delegate action on-chain |
313
+ | GET | `/near/relay-status/:txHash` | Check relayed transaction status |
314
+ | GET | `/near/relayer-info` | Get relayer accountId, mode, balance |
289
315
 
290
316
  ## Advanced Configuration
291
317
 
292
- For advanced use cases, you can customize the validation functions:
293
-
294
318
  ```ts title="advanced-auth.ts"
295
319
  import { betterAuth } from "better-auth";
296
320
  import { siwn } from "better-near-auth";
@@ -302,60 +326,51 @@ export const auth = betterAuth({
302
326
  plugins: [
303
327
  siwn({
304
328
  recipient: "myapp.com",
305
- anonymous: false, // Require email for users
329
+ anonymous: false,
306
330
  emailDomainName: "myapp.com",
307
- requireFullAccessKey: false, // Allow function call keys
308
-
309
- // Custom nonce generation
310
- getNonce: async () => {
311
- return generateNonce();
312
- },
313
-
314
- // Custom nonce validation (prevents replay attacks)
331
+ requireFullAccessKey: false,
332
+
333
+ getNonce: async () => generateNonce(),
334
+
315
335
  validateNonce: (nonce: Uint8Array) => {
316
336
  const nonceHex = Array.from(nonce).map(b => b.toString(16).padStart(2, '0')).join('');
317
- if (usedNonces.has(nonceHex)) {
318
- return false; // Prevent replay attacks
319
- }
337
+ if (usedNonces.has(nonceHex)) return false;
320
338
  usedNonces.add(nonceHex);
321
339
  return true;
322
340
  },
323
-
324
- // Custom recipient validation (allow multiple domains)
341
+
325
342
  validateRecipient: (recipient: string) => {
326
- const allowedRecipients = ["myapp.com", "staging.myapp.com", "localhost:3000"];
327
- return allowedRecipients.includes(recipient);
343
+ return ["myapp.com", "staging.myapp.com"].includes(recipient);
328
344
  },
329
-
330
- // Custom message validation
345
+
331
346
  validateMessage: (message: string) => {
332
- // Add custom message format validation
333
347
  return message.includes("Sign in to") && message.length > 10;
334
348
  },
335
-
336
- // Custom profile lookup
349
+
337
350
  getProfile: async (accountId) => {
338
- // Custom profile logic, falls back to NEAR Social
339
351
  try {
340
- const response = await fetch(`https://api.myapp.com/profiles/${accountId}`);
341
- if (response.ok) {
342
- const customProfile = await response.json();
343
- return {
344
- name: customProfile.displayName,
345
- description: customProfile.bio,
346
- image: { url: customProfile.avatar },
347
- };
352
+ const res = await fetch(`https://api.myapp.com/profiles/${accountId}`);
353
+ if (res.ok) {
354
+ const p = await res.json();
355
+ return { name: p.displayName, description: p.bio, image: { url: p.avatar } };
348
356
  }
349
- } catch (error) {
350
- console.error("Custom profile fetch failed:", error);
351
- }
352
- return null; // Use default NEAR Social lookup
357
+ } catch {}
358
+ return null;
353
359
  },
354
-
355
- // Validate function call keys against allowed contracts
360
+
356
361
  validateLimitedAccessKey: async ({ accountId, publicKey, recipient }) => {
357
- const allowedContracts = ["myapp.near", "social.near"];
358
- return recipient ? allowedContracts.includes(recipient) : true;
362
+ const allowed = ["myapp.near", "social.near"];
363
+ return recipient ? allowed.includes(recipient) : true;
364
+ },
365
+
366
+ fastnearApiKey: process.env.FASTNEAR_API_KEY,
367
+
368
+ relayer: {
369
+ accountId: "relayer.myapp.near",
370
+ privateKey: process.env.RELAYER_PRIVATE_KEY,
371
+ whitelistedContracts: ["myapp.near"],
372
+ maxGasPerTransaction: "300000000000000",
373
+ maxDepositPerTransaction: "0",
359
374
  },
360
375
  }),
361
376
  ],
@@ -364,80 +379,51 @@ export const auth = betterAuth({
364
379
 
365
380
  ## Network Support
366
381
 
367
- The plugin automatically detects the network from the account ID:
382
+ The plugin detects the network from the account ID:
368
383
 
369
- - Accounts ending with `.testnet` use the testnet network
370
- - All other accounts use the mainnet network
384
+ - Accounts ending with `.testnet` testnet
385
+ - All other accounts mainnet
371
386
 
372
- You can configure the client to use a specific network:
373
-
374
- ```ts title="testnet-config.ts"
375
- export const authClient = createAuthClient({
376
- plugins: [
377
- siwnClient({
378
- domain: "myapp.com",
379
- networkId: "testnet", // Use testnet
380
- }),
381
- ],
382
- });
383
- ```
384
-
385
- ## Security Features
387
+ ## Security
386
388
 
387
389
  ### NEP-413 Compliance
388
- - Follows NEAR Enhancement Proposal 413 for secure message signing
389
- - Implements proper nonce handling to prevent replay attacks
390
- - Validates message format and recipient information
390
+ - Proper nonce handling prevents replay attacks
391
+ - Message format and recipient validation
392
+ - 15-minute server-side nonce expiration, 5-minute client-side cache
391
393
 
392
- ### Nonce Management
393
- - Unique nonce storage per account/network/publicKey combination
394
- - 15-minute server-side expiration for nonces
395
- - 5-minute client-side cache expiration
396
- - Automatic cleanup after successful authentication
394
+ ### Relayer Key Security
395
+ - Ephemeral private key encrypted at rest with AES-256-GCM
396
+ - KEK derived from `BETTER_AUTH_SECRET` via HKDF-SHA256
397
+ - Private key held only in process memory — never in env vars or config files
398
+ - Trust model matches Better Auth session tokens: DB access + secret = full access
397
399
 
398
400
  ### Access Key Support
399
- - Supports both full access keys and function call access keys
401
+ - Full access keys and function-call access keys (FAK)
402
+ - FAK scoped to recipient contract for delegate actions
400
403
  - Configurable validation for limited access keys
401
- - Contract-specific access control when using function call keys
402
404
 
403
405
  ## Troubleshooting
404
406
 
405
- ### Common Issues
406
-
407
- 1. **"Wallet not connected"**
408
- - You must call `requestSignIn.near()` before `signIn.near()`
409
- - Check that the near-kit client is properly initialized
410
-
411
- 2. **"No valid nonce found"**
412
- - Ensure `requestSignIn.near()` completed successfully before calling `signIn.near()`
413
- - Client nonces expire after 5 minutes
414
-
415
- 3. **"Invalid or expired nonce"**
416
- - Server nonces expire after 15 minutes
417
- - Ensure client and server clocks are synchronized
418
-
419
- 4. **"Account ID mismatch"**
420
- - Verify the signed message contains the correct account ID
421
- - Check for wallet switching between the two authentication steps
422
-
423
- 5. **"Network ID mismatch"**
424
- - Ensure the networkId sent to the server matches the account's network
425
- - Testnet accounts must use "testnet", mainnet accounts use "mainnet"
426
-
407
+ | Issue | Solution |
408
+ |---|---|
409
+ | "Wallet not connected" | Call `requestSignIn.near()` before `signIn.near()` |
410
+ | "No valid nonce found" | Ensure `requestSignIn.near()` completed; client nonces expire after 5 min |
411
+ | "Invalid or expired nonce" | Server nonces expire after 15 min; check clock sync |
412
+ | "Account ID mismatch" | Verify signed message account ID matches wallet |
413
+ | "Network ID mismatch" | Ensure networkId matches the account's network |
414
+ | Relay fails with "insufficient balance" | Fund the relayer account with NEAR |
415
+ | Relay fails with "contract not whitelisted" | Add `receiverId` to `whitelistedContracts` |
427
416
 
428
417
  ## Examples
429
418
 
430
- This repository includes example applications demonstrating how to use better-near-auth:
431
-
432
419
  ### Browser to Server Example
433
420
 
434
- A full-stack example showing NEAR authentication in a browser app with a server backend.
421
+ A full-stack example showing NEAR authentication + gasless relay.
435
422
 
436
423
  - **Location**: `examples/browser-2-server/`
437
424
  - **Live Demo**: [better-near-auth.near.page](https://better-near-auth.near.page)
438
425
  - **Tech Stack**: Hono, Drizzle ORM, React, TanStack Router
439
426
 
440
- **Running locally:**
441
427
  ```bash
442
428
  # From repo root
443
429
  pnpm install
@@ -445,54 +431,30 @@ cd examples/browser-2-server
445
431
  pnpm dev
446
432
  ```
447
433
 
448
- **Deployment:**
449
- Each example can be deployed independently to Railway or other platforms. See the example's README for deployment instructions.
450
-
451
434
  ## Development
452
435
 
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
436
+ Interested in contributing? See [CONTRIBUTING.md](./CONTRIBUTING.md).
458
437
 
459
438
  **Quick start:**
460
439
  ```bash
461
- # Install dependencies
462
440
  pnpm install
463
-
464
- # Build the package
465
441
  pnpm build
466
-
467
- # Run type checking
468
442
  pnpm typecheck
469
-
470
- # Run tests
471
443
  pnpm test
472
-
473
- # Run example locally
474
- cd examples/browser-2-server && pnpm dev
475
444
  ```
476
445
 
477
446
  **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
447
+ - `dist/index.js` Server plugin (ESM)
448
+ - `dist/client.js` Client plugin (ESM)
449
+ - `dist/*.d.ts` TypeScript declarations
488
450
 
489
451
  ## Links
490
452
 
491
- * [Better Auth Documentation](https://better-auth.com)
492
- * [NEAR Protocol](https://near.org)
493
- * [NEP-413 Specification](https://github.com/near/NEPs/blob/master/neps/nep-0413.md)
494
- * [near-sign-verify](https://github.com/elliotBraem/near-sign-verify)
495
- * [near-kit](https://kit.near.tools/)
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
453
+ - [Better Auth Documentation](https://better-auth.com)
454
+ - [NEAR Protocol](https://near.org)
455
+ - [NEP-413 Specification](https://github.com/near/NEPs/blob/master/neps/nep-0413.md)
456
+ - [NEP-366 Delegate Actions](https://github.com/near/NEPs/blob/master/neps/nep-0366.md)
457
+ - [FastNear](https://fastnear.com)
458
+ - [near-sign-verify](https://github.com/elliotBraem/near-sign-verify)
459
+ - [Example Implementation](https://better-near-auth.near.page)
460
+ - [Contributing Guide](./CONTRIBUTING.md)