emblem-vault-ai-signers 0.1.7 → 0.1.8-experimental.1

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
@@ -20,6 +20,9 @@ See the Changelog for release details: CHANGELOG.md
20
20
  npm install emblem-vault-ai-signers
21
21
  # and bring your own peers
22
22
  npm install ethers viem
23
+
24
+ # Optional: for SDK integration
25
+ npm install emblem-auth-sdk
23
26
  ```
24
27
 
25
28
  ## Usage
@@ -31,7 +34,11 @@ import { mainnet } from "viem/chains";
31
34
  import { JsonRpcProvider } from "ethers";
32
35
 
33
36
  const client = createEmblemClient({
34
- apiKey: "your-x-api-key",
37
+ apiKey: "your-x-api-key", // traditional API key auth
38
+ // OR use JWT authentication (see Authentication section below)
39
+ // jwt: "your-jwt-token",
40
+ // OR use SDK integration
41
+ // sdk: yourAuthSDK,
35
42
  // baseUrl: "https://api.emblemvault.ai" // optional (tests use https://dev-api.emblemvault.ai)
36
43
  });
37
44
 
@@ -66,6 +73,142 @@ const solKit = await client.toSolanaKitSigner();
66
73
  console.log(solKit.publicKey);
67
74
  ```
68
75
 
76
+ ## Authentication
77
+
78
+ The library supports multiple authentication methods. You only need to provide **one** of the following:
79
+
80
+ ### API Key Authentication (Traditional)
81
+
82
+ ```ts
83
+ const client = createEmblemClient({
84
+ apiKey: "pk_your_api_key_here"
85
+ });
86
+ ```
87
+
88
+ ### JWT Authentication
89
+
90
+ #### Static JWT Token
91
+ ```ts
92
+ const client = createEmblemClient({
93
+ jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
94
+ });
95
+ ```
96
+
97
+ #### Dynamic JWT Provider
98
+ For tokens that need refreshing or are fetched asynchronously:
99
+ ```ts
100
+ const client = createEmblemClient({
101
+ getJwt: async () => {
102
+ // Fetch from your auth service, refresh if needed
103
+ return await authService.getAccessToken();
104
+ }
105
+ });
106
+
107
+ // Or synchronous
108
+ const client = createEmblemClient({
109
+ getJwt: () => localStorage.getItem('authToken')
110
+ });
111
+ ```
112
+
113
+ ### SDK Integration
114
+
115
+ If you're using an authentication SDK that manages sessions:
116
+ ```ts
117
+ const client = createEmblemClient({
118
+ sdk: myAuthSDK // Must have getSession() method that returns { authToken }
119
+ });
120
+
121
+ // Example with EmblemAuthSDK:
122
+ import { EmblemAuthSDK } from 'emblem-auth-sdk';
123
+
124
+ const authSDK = new EmblemAuthSDK({
125
+ appId: 'your-app-id',
126
+ apiUrl: 'https://api.emblemvault.ai'
127
+ });
128
+
129
+ const client = createEmblemClient({
130
+ sdk: authSDK // EmblemAuthSDK has getSession() that returns { authToken, user, ... }
131
+ });
132
+
133
+ // Example with custom auth SDK:
134
+ const client = createEmblemClient({
135
+ sdk: {
136
+ getSession: () => ({
137
+ authToken: auth0.getIdToken(),
138
+ user: { id: '123' }
139
+ })
140
+ }
141
+ });
142
+ ```
143
+
144
+ ### Custom Auth Headers
145
+
146
+ For advanced authentication schemes:
147
+ ```ts
148
+ const client = createEmblemClient({
149
+ getAuthHeaders: async () => ({
150
+ 'Authorization': 'Custom my-custom-token',
151
+ 'X-API-Version': '2.0',
152
+ 'X-Client-ID': 'my-app'
153
+ })
154
+ });
155
+ ```
156
+
157
+ ### Authentication Priority
158
+
159
+ When multiple auth methods are provided, they're used in this order:
160
+ 1. `getAuthHeaders()` (highest priority)
161
+ 2. `apiKey`
162
+ 3. `jwt` / `getJwt()` / `sdk` (lowest priority)
163
+
164
+ ### Browser vs Server Usage
165
+
166
+ - **Browser**: JWT/SDK authentication is recommended for client-side apps where users authenticate themselves
167
+ - **Server**: API key authentication is recommended for server-side applications with stored credentials
168
+
169
+ ### Complete SDK Integration Example
170
+
171
+ Here's a complete example using the EmblemAuthSDK with the signers library:
172
+
173
+ ```ts
174
+ import { EmblemAuthSDK } from 'emblem-auth-sdk';
175
+ import { createEmblemClient } from 'emblem-vault-ai-signers';
176
+ import { JsonRpcProvider, parseEther } from 'ethers';
177
+
178
+ // 1. Initialize the auth SDK
179
+ const authSDK = new EmblemAuthSDK({
180
+ appId: 'your-app-id',
181
+ apiUrl: 'https://api.emblemvault.ai',
182
+ onSuccess: (session) => {
183
+ console.log('User authenticated:', session.user);
184
+ }
185
+ });
186
+
187
+ // 2. Create the signer client using the SDK
188
+ const client = createEmblemClient({
189
+ sdk: authSDK // Pass the SDK instance directly
190
+ });
191
+
192
+ // 3. Authenticate the user (opens auth modal)
193
+ await authSDK.openAuthModal();
194
+
195
+ // 4. Once authenticated, create wallets/accounts
196
+ const provider = new JsonRpcProvider(process.env.RPC_URL);
197
+ const wallet = await client.toEthersWallet(provider);
198
+
199
+ // 5. Sign and send transactions
200
+ const txHash = await wallet.signAndBroadcast({
201
+ to: "0x...",
202
+ value: parseEther("0.01")
203
+ });
204
+ ```
205
+
206
+ The SDK integration automatically handles:
207
+ - JWT token management and refresh
208
+ - Session persistence across page reloads
209
+ - Authentication state management
210
+ - Seamless integration with the signers library
211
+
69
212
  ## Replace Private Keys (Examples)
70
213
 
71
214
  Below are quick swaps showing how to remove local private keys and route signing through Emblem.
@@ -175,7 +318,13 @@ console.log(solWeb3.publicKey);
175
318
 
176
319
  ```ts
177
320
  type EmblemRemoteConfig = {
178
- apiKey: string;
321
+ // Authentication (pick one method):
322
+ apiKey?: string; // traditional x-api-key header
323
+ jwt?: string; // static JWT for Authorization: Bearer
324
+ getJwt?: () => Promise<string> | string; // dynamic JWT provider
325
+ getAuthHeaders?: () => Promise<Record<string, string>> | Record<string, string>; // custom auth headers
326
+ sdk?: { getSession: () => { authToken?: string } | null }; // SDK integration (e.g., EmblemAuthSDK)
327
+
179
328
  baseUrl?: string; // default https://api.emblemvault.ai
180
329
  };
181
330
 
@@ -200,7 +349,7 @@ Adapters POST to the Emblem API endpoints:
200
349
  - `POST /sign-typed-message` – `{ vaultId, domain, types, message }`
201
350
  - `POST /sign-eth-tx` – `{ vaultId, transaction }` (expects ethers-serializable fields)
202
351
 
203
- On first use, both adapters query `GET /vault/info` with header `x-api-key` to obtain:
352
+ On first use, both adapters query `POST /vault/info` with authentication headers to obtain:
204
353
 
205
354
  - Vault ID
206
355
  - Solana Address
@@ -222,15 +371,20 @@ This library is designed for environments where **users provide their own API ke
222
371
 
223
372
  #### What This Means
224
373
  ```javascript
225
- // Users provide their OWN API keys to YOUR dApp
374
+ // Users provide their OWN credentials to YOUR dApp
226
375
  const client = createEmblemClient({
376
+ // Traditional API key
227
377
  apiKey: userApiKey, // User's key, not yours
378
+ // OR JWT token from user's authentication
379
+ jwt: userJwtToken, // User's JWT, not yours
380
+ // OR SDK integration
381
+ sdk: userAuthSDK, // User's auth SDK
228
382
  baseUrl: "https://api.emblemvault.ai"
229
383
  });
230
384
  ```
231
385
 
232
- If a user runs your dApp code, they are trusting it with their API key and signing authority. There is no way to prevent malicious dApp code from:
233
- - Logging API keys
386
+ If a user runs your dApp code, they are trusting it with their authentication credentials and signing authority. There is no way to prevent malicious dApp code from:
387
+ - Logging API keys or JWT tokens
234
388
  - Intercepting `fetch()` calls
235
389
  - Changing the `baseUrl`
236
390
  - Making unauthorized signing requests
@@ -241,9 +395,10 @@ If a user runs your dApp code, they are trusting it with their API key and signi
241
395
 
242
396
  1. **Only use trusted dApps** - Verify the source and reputation
243
397
  2. **Review open source code** when possible
244
- 3. **Use separate API keys** for different dApps
398
+ 3. **Use separate credentials** for different dApps (API keys, JWT tokens)
245
399
  4. **Monitor signing activity** in your Emblem dashboard
246
- 5. **Test with staging keys first** before using production
400
+ 5. **Test with staging credentials first** before using production
401
+ 6. **Understand token expiration** - JWT tokens expire and may need refresh
247
402
 
248
403
  ### Best Practices for Implementers
249
404
 
@@ -251,16 +406,20 @@ If a user runs your dApp code, they are trusting it with their API key and signi
251
406
  2. **Document your security model** - Be transparent about API key handling
252
407
  3. **Minimize dependencies** - Reduce supply chain attack surface
253
408
  4. **Use Content Security Policy** - Add CSP headers to protect against XSS
254
- 5. **Never log or store user API keys** - Only use them in-memory for signing
255
- 6. **Implement proper error handling** - Don't expose API keys in error messages
409
+ 5. **Never log or store user credentials** - Only use API keys/JWTs in-memory for signing
410
+ 6. **Implement proper error handling** - Don't expose credentials in error messages
411
+ 7. **Handle JWT expiration gracefully** - Implement token refresh when using dynamic JWTs
412
+ 8. **Prefer JWT auth for client-side apps** - More secure than exposing long-lived API keys
256
413
 
257
414
  ### Server-Side Usage
258
415
 
259
416
  When used server-side (Node.js):
260
417
  - Store API keys in environment variables
261
- - Never expose keys to client-side code
418
+ - Never expose credentials to client-side code
262
419
  - Use proper access controls and authentication
263
420
  - Implement rate limiting if exposing signing endpoints
421
+ - Consider API key auth for server-to-server communication
422
+ - Use JWT auth when proxying user authentication
264
423
 
265
424
  ### Development vs Production
266
425
 
@@ -273,14 +432,20 @@ const devClient = createEmblemClient({
273
432
  baseUrl: "https://dev-api.emblemvault.ai"
274
433
  });
275
434
 
276
- // Production
435
+ // Production with API key
277
436
  const prodClient = createEmblemClient({
278
437
  apiKey: process.env.PROD_API_KEY,
279
438
  baseUrl: "https://api.emblemvault.ai"
280
439
  });
440
+
441
+ // Production with JWT (client-side)
442
+ const jwtClient = createEmblemClient({
443
+ getJwt: async () => await auth.getAccessToken(),
444
+ baseUrl: "https://api.emblemvault.ai"
445
+ });
281
446
  ```
282
447
 
283
- API keys from one environment do not work in another, providing natural isolation.
448
+ Credentials from one environment do not work in another, providing natural isolation.
284
449
 
285
450
  ---
286
451
 
package/dist/http.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import type { EmblemRemoteConfig } from "./types.js";
2
- export declare function emblemPost<T = any>(path: string, body: any, { apiKey, baseUrl }: EmblemRemoteConfig): Promise<T>;
3
- export declare function emblemGet<T = any>(path: string, { apiKey, baseUrl }: EmblemRemoteConfig): Promise<T>;
2
+ export declare function emblemPost<T = any>(path: string, body: any, config: EmblemRemoteConfig): Promise<T>;
3
+ export declare function emblemGet<T = any>(path: string, config: EmblemRemoteConfig): Promise<T>;
package/dist/http.js CHANGED
@@ -19,12 +19,30 @@ function sanitizeErrorMessage(status, text) {
19
19
  }
20
20
  return errorMessage;
21
21
  }
22
- export async function emblemPost(path, body, { apiKey, baseUrl = "https://api.emblemvault.ai" }) {
22
+ async function resolveAuthHeaders(config) {
23
+ // Priority: custom headers -> apiKey -> jwt/getJwt/sdk
24
+ if (typeof config.getAuthHeaders === 'function') {
25
+ const h = await config.getAuthHeaders();
26
+ if (h && typeof h === 'object')
27
+ return h;
28
+ }
29
+ if (config.apiKey) {
30
+ return { 'x-api-key': config.apiKey };
31
+ }
32
+ const tok = config.jwt ?? (typeof config.getJwt === 'function' ? await config.getJwt() : undefined) ?? (config.sdk?.getSession()?.authToken ?? undefined);
33
+ if (tok) {
34
+ return { 'Authorization': `Bearer ${tok}` };
35
+ }
36
+ throw new Error('No authentication available: provide apiKey, jwt, getJwt(), getAuthHeaders(), or sdk');
37
+ }
38
+ export async function emblemPost(path, body, config) {
39
+ const baseUrl = config.baseUrl ?? "https://api.emblemvault.ai";
40
+ const authHeaders = await resolveAuthHeaders(config);
23
41
  const res = await fetch(`${baseUrl}${path}`, {
24
42
  method: "POST",
25
43
  headers: {
26
44
  "content-type": "application/json",
27
- "x-api-key": apiKey,
45
+ ...authHeaders,
28
46
  },
29
47
  body: JSON.stringify(body, (key, value) => typeof value === "bigint" ? value.toString() : value),
30
48
  });
@@ -34,12 +52,12 @@ export async function emblemPost(path, body, { apiKey, baseUrl = "https://api.em
34
52
  }
35
53
  return res.json();
36
54
  }
37
- export async function emblemGet(path, { apiKey, baseUrl = "https://api.emblemvault.ai" }) {
55
+ export async function emblemGet(path, config) {
56
+ const baseUrl = config.baseUrl ?? "https://api.emblemvault.ai";
57
+ const authHeaders = await resolveAuthHeaders(config);
38
58
  const res = await fetch(`${baseUrl}${path}`, {
39
59
  method: "GET",
40
- headers: {
41
- "x-api-key": apiKey,
42
- },
60
+ headers: authHeaders,
43
61
  });
44
62
  if (!res.ok) {
45
63
  const text = await res.text().catch(() => "");
package/dist/types.d.ts CHANGED
@@ -1,8 +1,23 @@
1
1
  export type Hex = `0x${string}`;
2
+ /** Optional auth providers */
3
+ export type EmblemAuthProvider = {
4
+ /** Static JWT to send as Authorization: Bearer */
5
+ jwt?: string;
6
+ /** Lazy JWT provider */
7
+ getJwt?: () => Promise<string | null | undefined> | string | null | undefined;
8
+ /** Custom headers provider (e.g., Authorization) */
9
+ getAuthHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
10
+ /** SDK-like object exposing getSession() that returns { authToken? } */
11
+ sdk?: {
12
+ getSession: () => {
13
+ authToken?: string | null | undefined;
14
+ } | null | undefined;
15
+ };
16
+ };
2
17
  /** Config for Emblem remote signer */
3
- export type EmblemRemoteConfig = {
4
- /** x-api-key for your authenticate middleware */
5
- apiKey: string;
18
+ export type EmblemRemoteConfig = EmblemAuthProvider & {
19
+ /** x-api-key for your authenticate middleware (optional when JWT provided) */
20
+ apiKey?: string;
6
21
  /** Base URL for the Emblem signer API */
7
22
  baseUrl?: string;
8
23
  };
@@ -13,7 +13,7 @@ export declare function isNodeEnvironment(): boolean;
13
13
  /**
14
14
  * Validate API key format and warn about potential security issues
15
15
  */
16
- export declare function validateApiKey(apiKey: string, options?: {
16
+ export declare function validateApiKey(apiKey?: string, options?: {
17
17
  warnOnBrowser?: boolean;
18
18
  }): void;
19
19
  /**
@@ -19,24 +19,20 @@ export function isNodeEnvironment() {
19
19
  * Validate API key format and warn about potential security issues
20
20
  */
21
21
  export function validateApiKey(apiKey, options = {}) {
22
- if (!apiKey || typeof apiKey !== 'string') {
23
- throw new Error('apiKey is required and must be a string');
22
+ if (apiKey == null)
23
+ return; // optional
24
+ if (typeof apiKey !== 'string') {
25
+ throw new Error('apiKey must be a string when provided');
24
26
  }
25
27
  if (apiKey.trim() === '') {
26
28
  throw new Error('apiKey cannot be empty or whitespace');
27
29
  }
28
- // Warn if API key looks like it might be exposed in client-side code
29
30
  if (options.warnOnBrowser !== false && isBrowserEnvironment()) {
30
- console.warn('[Emblem Security Warning] API key is being used in a browser environment. ' +
31
- 'API keys should only be used server-side (Node.js). ' +
32
- 'Client-side usage exposes your API key to anyone who can view the page source. ' +
33
- 'To suppress this warning, pass { warnOnBrowser: false } to createEmblemClient().');
31
+ console.warn('[Emblem Security Warning] API key is being used in a browser environment. '
32
+ + 'Prefer JWT or a secure server-side usage. '
33
+ + 'To suppress this warning, pass { warnOnBrowser: false } to createEmblemClient().');
34
34
  }
35
- // Check for common mistakes
36
- if (apiKey.startsWith('pk_') || apiKey.startsWith('sk_')) {
37
- // Looks like a typical API key format - good
38
- }
39
- else if (apiKey.length < 16) {
35
+ if (!apiKey.startsWith('pk_') && !apiKey.startsWith('sk_') && apiKey.length < 16) {
40
36
  console.warn('[Emblem Security Warning] API key seems unusually short. Is this correct?');
41
37
  }
42
38
  }
@@ -93,7 +89,16 @@ export function toSafeNumber(value, fieldName) {
93
89
  * Comprehensive configuration validation
94
90
  */
95
91
  export function validateConfig(config) {
96
- // Validate API key
92
+ // Validate auth: require at least one method (apiKey, jwt, getJwt, getAuthHeaders, sdk)
93
+ const hasApiKey = !!config.apiKey;
94
+ const hasJwt = !!config.jwt;
95
+ const hasGetJwt = typeof config.getJwt === 'function';
96
+ const hasHeaders = typeof config.getAuthHeaders === 'function';
97
+ const hasSdk = !!config.sdk && typeof config.sdk.getSession === 'function';
98
+ if (!hasApiKey && !hasJwt && !hasGetJwt && !hasHeaders && !hasSdk) {
99
+ throw new Error('Authentication required: provide apiKey, jwt, getJwt(), getAuthHeaders(), or sdk');
100
+ }
101
+ // Validate API key if present
97
102
  validateApiKey(config.apiKey, { warnOnBrowser: config.warnOnBrowser });
98
103
  // Validate baseUrl if provided
99
104
  if (config.baseUrl) {
@@ -104,7 +109,7 @@ export function validateConfig(config) {
104
109
  console.log('[Emblem Security Debug]', {
105
110
  environment: isBrowserEnvironment() ? 'browser' : 'node',
106
111
  hasBaseUrl: !!config.baseUrl,
107
- apiKeyLength: config.apiKey.length,
112
+ apiKeyLength: config.apiKey ? config.apiKey.length : 0,
108
113
  timestamp: new Date().toISOString(),
109
114
  });
110
115
  }
package/dist/vault.js CHANGED
@@ -3,17 +3,17 @@ export async function fetchVaultInfo(config) {
3
3
  // Note: The server only supports POST for /vault/info
4
4
  // No need to try GET first, just use POST directly
5
5
  const data = await emblemPost("/vault/info", {}, config);
6
- // Validate response data
7
- if (!data.vaultId || !data.address || !data.evmAddress) {
6
+ // Validate required response data (vaultId + evmAddress are required for EVM)
7
+ if (!data || !data.vaultId || !data.evmAddress) {
8
8
  throw new Error('Invalid vault info response: missing required fields');
9
9
  }
10
- if (!data.evmAddress.startsWith('0x')) {
10
+ if (!String(data.evmAddress).startsWith('0x')) {
11
11
  throw new Error('Invalid evmAddress format in response');
12
12
  }
13
13
  return {
14
14
  vaultId: data.vaultId,
15
15
  tokenId: data.vaultId,
16
- address: data.address,
16
+ address: data.address || '', // Solana address may be absent; keep optional
17
17
  evmAddress: data.evmAddress,
18
18
  created_by: data.created_by,
19
19
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emblem-vault-ai-signers",
3
- "version": "0.1.7",
3
+ "version": "0.1.8-experimental.1",
4
4
  "description": "Emblem Vault remote signer adapters for viem and ethers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,7 +25,8 @@
25
25
  "test:ci": "vitest run --reporter=default",
26
26
  "test:integration": "vitest -c vitest.int.config.ts run --reporter=verbose",
27
27
  "test:all": "npm test && npm run test:integration",
28
- "release:patch": "npm version patch && npm run build && npm run test:all && npm publish"
28
+ "release:patch": "npm version patch && npm run build && npm run test:all && npm publish",
29
+ "release:experimental": "npm version prerelease --preid=experimental && npm run build && npm run test:all && npm publish --tag experimental"
29
30
  },
30
31
  "keywords": [
31
32
  "emblem",