emblem-vault-ai-signers 0.1.8-experimental.0 → 0.1.8-experimental.2

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
@@ -7,8 +7,8 @@ Remote signer adapters for Emblem Vault that plug into popular Ethereum librarie
7
7
  - Implements `initialize()`, `getVaultId()`, `setChainId()`, `getChainId()`
8
8
  - Adds `signAndBroadcast(tx, waitForReceipt?)` helper (optional)
9
9
  - `toWeb3Adapter()` – returns a minimal Web3-style signer adapter (EVM)
10
- - `toSolanaWeb3Signer()` – returns a stub Solana signer with `publicKey`
11
- - `toSolanaKitSigner()` – returns a stub Solana signer with `publicKey`
10
+ - `toSolanaWeb3Signer()` – creates a @solana/web3.js compatible signer
11
+ - `toSolanaKitSigner()` – creates a Solana Kit compatible signer
12
12
 
13
13
  > Note: The ethers adapter targets ethers v6.
14
14
 
@@ -20,6 +20,12 @@ 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 Solana support
25
+ npm install @solana/web3.js
26
+
27
+ # Optional: for SDK integration
28
+ npm install emblem-auth-sdk
23
29
  ```
24
30
 
25
31
  ## Usage
@@ -31,7 +37,11 @@ import { mainnet } from "viem/chains";
31
37
  import { JsonRpcProvider } from "ethers";
32
38
 
33
39
  const client = createEmblemClient({
34
- apiKey: "your-x-api-key",
40
+ apiKey: "your-x-api-key", // traditional API key auth
41
+ // OR use JWT authentication (see Authentication section below)
42
+ // jwt: "your-jwt-token",
43
+ // OR use SDK integration
44
+ // sdk: yourAuthSDK,
35
45
  // baseUrl: "https://api.emblemvault.ai" // optional (tests use https://dev-api.emblemvault.ai)
36
46
  });
37
47
 
@@ -59,13 +69,182 @@ const txHash = await wallet.signAndBroadcast({ to: "0x...", value: 1n }, true);
59
69
  const web3Adapter = await client.toWeb3Adapter();
60
70
  await web3Adapter.signMessage("hello");
61
71
 
62
- // Solana stubs (address only; signing not yet implemented)
72
+ // Solana (@solana/web3.js compatible)
63
73
  const solWeb3 = await client.toSolanaWeb3Signer();
64
- console.log(solWeb3.publicKey);
74
+ console.log(solWeb3.publicKey); // base58 Solana address
75
+
76
+ // Sign a message
77
+ const signature = await solWeb3.signMessage("Hello Solana");
78
+
79
+ // Sign a transaction
80
+ import { VersionedTransaction, TransactionMessage, SystemProgram, PublicKey } from "@solana/web3.js";
81
+
82
+ const fromPubkey = new PublicKey(solWeb3.publicKey);
83
+ const toPubkey = new PublicKey("...");
84
+
85
+ const messageV0 = new TransactionMessage({
86
+ payerKey: fromPubkey,
87
+ recentBlockhash: "...", // fetch from connection
88
+ instructions: [
89
+ SystemProgram.transfer({
90
+ fromPubkey,
91
+ toPubkey,
92
+ lamports: 1000000,
93
+ }),
94
+ ],
95
+ }).compileToV0Message();
96
+
97
+ const transaction = new VersionedTransaction(messageV0);
98
+ const signedTx = await solWeb3.signTransaction(transaction);
99
+
100
+ // Sign and broadcast
101
+ const txSignature = await solWeb3.signAndBroadcast(transaction, true);
102
+
103
+ // Utility methods
104
+ const vaultId = solWeb3.getVaultId();
105
+ const canSign = solWeb3.canSign(solWeb3.publicKey); // true
106
+
107
+ // Solana Kit compatible signer
65
108
  const solKit = await client.toSolanaKitSigner();
66
- console.log(solKit.publicKey);
109
+ // Same interface as solWeb3
110
+ ```
111
+
112
+ ## Authentication
113
+
114
+ The library supports multiple authentication methods. You only need to provide **one** of the following:
115
+
116
+ ### API Key Authentication (Traditional)
117
+
118
+ ```ts
119
+ const client = createEmblemClient({
120
+ apiKey: "pk_your_api_key_here"
121
+ });
122
+ ```
123
+
124
+ ### JWT Authentication
125
+
126
+ #### Static JWT Token
127
+ ```ts
128
+ const client = createEmblemClient({
129
+ jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
130
+ });
131
+ ```
132
+
133
+ #### Dynamic JWT Provider
134
+ For tokens that need refreshing or are fetched asynchronously:
135
+ ```ts
136
+ const client = createEmblemClient({
137
+ getJwt: async () => {
138
+ // Fetch from your auth service, refresh if needed
139
+ return await authService.getAccessToken();
140
+ }
141
+ });
142
+
143
+ // Or synchronous
144
+ const client = createEmblemClient({
145
+ getJwt: () => localStorage.getItem('authToken')
146
+ });
147
+ ```
148
+
149
+ ### SDK Integration
150
+
151
+ If you're using an authentication SDK that manages sessions:
152
+ ```ts
153
+ const client = createEmblemClient({
154
+ sdk: myAuthSDK // Must have getSession() method that returns { authToken }
155
+ });
156
+
157
+ // Example with EmblemAuthSDK:
158
+ import { EmblemAuthSDK } from 'emblem-auth-sdk';
159
+
160
+ const authSDK = new EmblemAuthSDK({
161
+ appId: 'your-app-id',
162
+ apiUrl: 'https://api.emblemvault.ai'
163
+ });
164
+
165
+ const client = createEmblemClient({
166
+ sdk: authSDK // EmblemAuthSDK has getSession() that returns { authToken, user, ... }
167
+ });
168
+
169
+ // Example with custom auth SDK:
170
+ const client = createEmblemClient({
171
+ sdk: {
172
+ getSession: () => ({
173
+ authToken: auth0.getIdToken(),
174
+ user: { id: '123' }
175
+ })
176
+ }
177
+ });
67
178
  ```
68
179
 
180
+ ### Custom Auth Headers
181
+
182
+ For advanced authentication schemes:
183
+ ```ts
184
+ const client = createEmblemClient({
185
+ getAuthHeaders: async () => ({
186
+ 'Authorization': 'Custom my-custom-token',
187
+ 'X-API-Version': '2.0',
188
+ 'X-Client-ID': 'my-app'
189
+ })
190
+ });
191
+ ```
192
+
193
+ ### Authentication Priority
194
+
195
+ When multiple auth methods are provided, they're used in this order:
196
+ 1. `getAuthHeaders()` (highest priority)
197
+ 2. `apiKey`
198
+ 3. `jwt` / `getJwt()` / `sdk` (lowest priority)
199
+
200
+ ### Browser vs Server Usage
201
+
202
+ - **Browser**: JWT/SDK authentication is recommended for client-side apps where users authenticate themselves
203
+ - **Server**: API key authentication is recommended for server-side applications with stored credentials
204
+
205
+ ### Complete SDK Integration Example
206
+
207
+ Here's a complete example using the EmblemAuthSDK with the signers library:
208
+
209
+ ```ts
210
+ import { EmblemAuthSDK } from 'emblem-auth-sdk';
211
+ import { createEmblemClient } from 'emblem-vault-ai-signers';
212
+ import { JsonRpcProvider, parseEther } from 'ethers';
213
+
214
+ // 1. Initialize the auth SDK
215
+ const authSDK = new EmblemAuthSDK({
216
+ appId: 'your-app-id',
217
+ apiUrl: 'https://api.emblemvault.ai',
218
+ onSuccess: (session) => {
219
+ console.log('User authenticated:', session.user);
220
+ }
221
+ });
222
+
223
+ // 2. Create the signer client using the SDK
224
+ const client = createEmblemClient({
225
+ sdk: authSDK // Pass the SDK instance directly
226
+ });
227
+
228
+ // 3. Authenticate the user (opens auth modal)
229
+ await authSDK.openAuthModal();
230
+
231
+ // 4. Once authenticated, create wallets/accounts
232
+ const provider = new JsonRpcProvider(process.env.RPC_URL);
233
+ const wallet = await client.toEthersWallet(provider);
234
+
235
+ // 5. Sign and send transactions
236
+ const txHash = await wallet.signAndBroadcast({
237
+ to: "0x...",
238
+ value: parseEther("0.01")
239
+ });
240
+ ```
241
+
242
+ The SDK integration automatically handles:
243
+ - JWT token management and refresh
244
+ - Session persistence across page reloads
245
+ - Authentication state management
246
+ - Seamless integration with the signers library
247
+
69
248
  ## Replace Private Keys (Examples)
70
249
 
71
250
  Below are quick swaps showing how to remove local private keys and route signing through Emblem.
@@ -162,20 +341,56 @@ const provider = new JsonRpcProvider(process.env.RPC_URL!);
162
341
  await provider.broadcastTransaction(rawTransaction);
163
342
  ```
164
343
 
165
- ### Solana (stubs)
344
+ ### Solana (@solana/web3.js)
166
345
 
167
- These expose the Solana address derived from the vault. Signing is not implemented yet.
346
+ Full Solana signing support with message and transaction signing via remote vault.
168
347
 
169
348
  ```ts
170
- const solWeb3 = await client.toSolanaWeb3Signer();
171
- console.log(solWeb3.publicKey);
349
+ const client = createEmblemClient({ apiKey: process.env.EMBLEM_API_KEY! });
350
+
351
+ // Create Solana signer
352
+ const solSigner = await client.toSolanaWeb3Signer();
353
+ console.log(solSigner.publicKey); // Base58 Solana address
354
+
355
+ // Sign messages
356
+ const message = "Hello Solana";
357
+ const signature = await solSigner.signMessage(message);
358
+ console.log(signature); // Uint8Array signature
359
+
360
+ // Sign transactions
361
+ const transaction = new Transaction()
362
+ .add(SystemProgram.transfer({
363
+ fromPubkey: new PublicKey(solSigner.publicKey),
364
+ toPubkey: new PublicKey("recipient-address"),
365
+ lamports: 1000000 // 0.001 SOL
366
+ }));
367
+
368
+ // Option 1: Sign only
369
+ const signedTx = await solSigner.signTransaction(transaction);
370
+
371
+ // Option 2: Sign and broadcast
372
+ const txSignature = await solSigner.signAndBroadcast(transaction, true);
373
+ console.log("Transaction signature:", txSignature);
374
+
375
+ // Utility methods
376
+ console.log("Vault ID:", solSigner.getVaultId());
377
+ console.log("Can sign for this key?", solSigner.canSign(solSigner.publicKey));
378
+
379
+ // Both @solana/web3.js and Solana Kit compatible
380
+ const solKit = await client.toSolanaKitSigner(); // Same interface
172
381
  ```
173
382
 
174
383
  ## API
175
384
 
176
385
  ```ts
177
386
  type EmblemRemoteConfig = {
178
- apiKey: string;
387
+ // Authentication (pick one method):
388
+ apiKey?: string; // traditional x-api-key header
389
+ jwt?: string; // static JWT for Authorization: Bearer
390
+ getJwt?: () => Promise<string> | string; // dynamic JWT provider
391
+ getAuthHeaders?: () => Promise<Record<string, string>> | Record<string, string>; // custom auth headers
392
+ sdk?: { getSession: () => { authToken?: string } | null }; // SDK integration (e.g., EmblemAuthSDK)
393
+
179
394
  baseUrl?: string; // default https://api.emblemvault.ai
180
395
  };
181
396
 
@@ -183,8 +398,8 @@ createEmblemClient(config): EmblemVaultClient
183
398
  EmblemVaultClient#toViemAccount(): Promise<Account>
184
399
  EmblemVaultClient#toEthersWallet(provider?): Promise<Signer>
185
400
  EmblemVaultClient#toWeb3Adapter(): Promise<{ address, signMessage, signTypedData, signTransaction }>
186
- EmblemVaultClient#toSolanaWeb3Signer(): Promise<{ publicKey }>
187
- EmblemVaultClient#toSolanaKitSigner(): Promise<{ publicKey }>
401
+ EmblemVaultClient#toSolanaWeb3Signer(): Promise<{ publicKey, signMessage, signTransaction, signAndBroadcast, getVaultId, canSign, signAllTransactions }>
402
+ EmblemVaultClient#toSolanaKitSigner(): Promise<{ publicKey, signMessage, signTransaction, signAndBroadcast, getVaultId, canSign, signAllTransactions }>
188
403
 
189
404
  Ethers wallet (v6) adds:
190
405
  - initialize(): Promise<void>
@@ -196,11 +411,16 @@ Ethers wallet (v6) adds:
196
411
 
197
412
  Adapters POST to the Emblem API endpoints:
198
413
 
414
+ **EVM:**
199
415
  - `POST /sign-eth-message` – `{ vaultId, message }`
200
416
  - `POST /sign-typed-message` – `{ vaultId, domain, types, message }`
201
417
  - `POST /sign-eth-tx` – `{ vaultId, transaction }` (expects ethers-serializable fields)
202
418
 
203
- On first use, both adapters query `GET /vault/info` with header `x-api-key` to obtain:
419
+ **Solana:**
420
+ - `POST /sign-solana-message` – `{ vaultId, message }` (base64 encoded)
421
+ - `POST /sign-solana-transaction` – `{ vaultId, transactionToSign, broadcast, versionedTransaction }` (base64 serialized)
422
+
423
+ On first use, both adapters query `POST /vault/info` with authentication headers to obtain:
204
424
 
205
425
  - Vault ID
206
426
  - Solana Address
@@ -208,6 +428,42 @@ On first use, both adapters query `GET /vault/info` with header `x-api-key` to o
208
428
 
209
429
  Transactions are normalized to hex/number-like fields before submission.
210
430
 
431
+ ### Solana
432
+
433
+ Old (local keypair):
434
+ ```ts
435
+ import { Keypair, Transaction } from "@solana/web3.js";
436
+
437
+ const keypair = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY!));
438
+ const transaction = new Transaction().add(...);
439
+ transaction.sign(keypair);
440
+ ```
441
+
442
+ New (Emblem remote signer):
443
+ ```ts
444
+ import { createEmblemClient } from "emblem-vault-ai-signers";
445
+ import { VersionedTransaction, TransactionMessage } from "@solana/web3.js";
446
+
447
+ const client = createEmblemClient({ apiKey: process.env.EMBLEM_API_KEY! });
448
+ const signer = await client.toSolanaWeb3Signer();
449
+
450
+ // Sign messages
451
+ const msgSignature = await signer.signMessage("Hello Solana");
452
+
453
+ // Sign versioned transactions
454
+ const messageV0 = new TransactionMessage({
455
+ payerKey: new PublicKey(signer.publicKey),
456
+ recentBlockhash: "...",
457
+ instructions: [...]
458
+ }).compileToV0Message();
459
+
460
+ const transaction = new VersionedTransaction(messageV0);
461
+ const signedTx = await signer.signTransaction(transaction);
462
+
463
+ // Sign and broadcast
464
+ const txSig = await signer.signAndBroadcast(transaction, true);
465
+ ```
466
+
211
467
  ## Security Considerations
212
468
 
213
469
  ### Client-Side Usage
@@ -222,15 +478,20 @@ This library is designed for environments where **users provide their own API ke
222
478
 
223
479
  #### What This Means
224
480
  ```javascript
225
- // Users provide their OWN API keys to YOUR dApp
481
+ // Users provide their OWN credentials to YOUR dApp
226
482
  const client = createEmblemClient({
483
+ // Traditional API key
227
484
  apiKey: userApiKey, // User's key, not yours
485
+ // OR JWT token from user's authentication
486
+ jwt: userJwtToken, // User's JWT, not yours
487
+ // OR SDK integration
488
+ sdk: userAuthSDK, // User's auth SDK
228
489
  baseUrl: "https://api.emblemvault.ai"
229
490
  });
230
491
  ```
231
492
 
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
493
+ 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:
494
+ - Logging API keys or JWT tokens
234
495
  - Intercepting `fetch()` calls
235
496
  - Changing the `baseUrl`
236
497
  - Making unauthorized signing requests
@@ -241,9 +502,10 @@ If a user runs your dApp code, they are trusting it with their API key and signi
241
502
 
242
503
  1. **Only use trusted dApps** - Verify the source and reputation
243
504
  2. **Review open source code** when possible
244
- 3. **Use separate API keys** for different dApps
505
+ 3. **Use separate credentials** for different dApps (API keys, JWT tokens)
245
506
  4. **Monitor signing activity** in your Emblem dashboard
246
- 5. **Test with staging keys first** before using production
507
+ 5. **Test with staging credentials first** before using production
508
+ 6. **Understand token expiration** - JWT tokens expire and may need refresh
247
509
 
248
510
  ### Best Practices for Implementers
249
511
 
@@ -251,16 +513,20 @@ If a user runs your dApp code, they are trusting it with their API key and signi
251
513
  2. **Document your security model** - Be transparent about API key handling
252
514
  3. **Minimize dependencies** - Reduce supply chain attack surface
253
515
  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
516
+ 5. **Never log or store user credentials** - Only use API keys/JWTs in-memory for signing
517
+ 6. **Implement proper error handling** - Don't expose credentials in error messages
518
+ 7. **Handle JWT expiration gracefully** - Implement token refresh when using dynamic JWTs
519
+ 8. **Prefer JWT auth for client-side apps** - More secure than exposing long-lived API keys
256
520
 
257
521
  ### Server-Side Usage
258
522
 
259
523
  When used server-side (Node.js):
260
524
  - Store API keys in environment variables
261
- - Never expose keys to client-side code
525
+ - Never expose credentials to client-side code
262
526
  - Use proper access controls and authentication
263
527
  - Implement rate limiting if exposing signing endpoints
528
+ - Consider API key auth for server-to-server communication
529
+ - Use JWT auth when proxying user authentication
264
530
 
265
531
  ### Development vs Production
266
532
 
@@ -273,14 +539,20 @@ const devClient = createEmblemClient({
273
539
  baseUrl: "https://dev-api.emblemvault.ai"
274
540
  });
275
541
 
276
- // Production
542
+ // Production with API key
277
543
  const prodClient = createEmblemClient({
278
544
  apiKey: process.env.PROD_API_KEY,
279
545
  baseUrl: "https://api.emblemvault.ai"
280
546
  });
547
+
548
+ // Production with JWT (client-side)
549
+ const jwtClient = createEmblemClient({
550
+ getJwt: async () => await auth.getAccessToken(),
551
+ baseUrl: "https://api.emblemvault.ai"
552
+ });
281
553
  ```
282
554
 
283
- API keys from one environment do not work in another, providing natural isolation.
555
+ Credentials from one environment do not work in another, providing natural isolation.
284
556
 
285
557
  ---
286
558
 
package/dist/solana.d.ts CHANGED
@@ -1,9 +1,31 @@
1
1
  import type { EmblemRemoteConfig, VaultInfo } from "./types.js";
2
- export declare class EmblemSolanaSigner {
2
+ export interface SolanaSignerInterface {
3
+ publicKey: string;
4
+ signMessage(message: Uint8Array | string): Promise<Uint8Array>;
5
+ signTransaction(transaction: any): Promise<any>;
6
+ }
7
+ export interface SolanaKitSignerInterface {
8
+ publicKey: string;
9
+ signMessage(message: Uint8Array | string): Promise<Uint8Array>;
10
+ signTransaction(transaction: any): Promise<any>;
11
+ }
12
+ export declare class EmblemSolanaSigner implements SolanaSignerInterface, SolanaKitSignerInterface {
3
13
  readonly publicKey: string;
4
- constructor(publicKey: string);
5
- signMessage(_message: Uint8Array | string): Promise<string>;
6
- signTransaction(_tx: unknown): Promise<string>;
14
+ private readonly config;
15
+ private readonly vaultId;
16
+ constructor(config: EmblemRemoteConfig, vaultInfo: VaultInfo);
17
+ signMessage(message: Uint8Array | string): Promise<Uint8Array>;
18
+ signTransaction(transaction: any): Promise<any>;
19
+ private serializeTransaction;
20
+ private deserializeTransaction;
21
+ /** Get the vault ID for this signer */
22
+ getVaultId(): string;
23
+ /** Sign multiple transactions in batch */
24
+ signAllTransactions(transactions: any[]): Promise<any[]>;
25
+ /** Check if this signer can sign for a given public key */
26
+ canSign(publicKey: string): boolean;
27
+ /** Sign and optionally broadcast a transaction */
28
+ signAndBroadcast(transaction: any, broadcast?: boolean): Promise<string>;
7
29
  }
8
30
  export declare function toSolanaWeb3Signer(config: EmblemRemoteConfig, infoOverride?: VaultInfo): Promise<EmblemSolanaSigner>;
9
31
  export declare function toSolanaKitSigner(config: EmblemRemoteConfig, infoOverride?: VaultInfo): Promise<EmblemSolanaSigner>;
package/dist/solana.js CHANGED
@@ -1,20 +1,149 @@
1
+ import { emblemPost } from "./http.js";
1
2
  import { fetchVaultInfo } from "./vault.js";
2
3
  export class EmblemSolanaSigner {
3
- constructor(publicKey) {
4
- this.publicKey = publicKey;
4
+ constructor(config, vaultInfo) {
5
+ this.publicKey = vaultInfo.address;
6
+ this.config = config;
7
+ this.vaultId = vaultInfo.vaultId;
5
8
  }
6
- async signMessage(_message) {
7
- throw new Error("Solana signing via Emblem is not implemented yet");
9
+ async signMessage(message) {
10
+ // Convert message to bytes if it's a string
11
+ const messageBytes = typeof message === 'string'
12
+ ? new TextEncoder().encode(message)
13
+ : message;
14
+ // Convert to base64 for API transmission
15
+ const messageBase64 = btoa(String.fromCharCode(...messageBytes));
16
+ const response = await emblemPost("/sign-solana-message", { vaultId: this.vaultId, message: messageBase64 }, this.config);
17
+ // The server returns a signature - could be base64, base58, or hex
18
+ // Let's check what format we're getting and handle accordingly
19
+ // Try to decode as base58 first (Solana standard)
20
+ try {
21
+ // Use @solana/web3.js bs58 decoder if available, otherwise fallback
22
+ if (typeof window !== 'undefined' && window.bs58) {
23
+ return window.bs58.decode(response.signature);
24
+ }
25
+ // For Node.js environments or if bs58 is not globally available
26
+ // The signature might be base64 encoded
27
+ const signatureBytes = Uint8Array.from(atob(response.signature), c => c.charCodeAt(0));
28
+ return signatureBytes;
29
+ }
30
+ catch (e) {
31
+ // If base64 decode fails, try treating as hex
32
+ if (response.signature.startsWith('0x')) {
33
+ const hex = response.signature.slice(2);
34
+ const bytes = new Uint8Array(hex.length / 2);
35
+ for (let i = 0; i < hex.length; i += 2) {
36
+ bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
37
+ }
38
+ return bytes;
39
+ }
40
+ throw new Error(`Unable to decode signature format: ${e}`);
41
+ }
8
42
  }
9
- async signTransaction(_tx) {
10
- throw new Error("Solana transaction signing via Emblem is not implemented yet");
43
+ async signTransaction(transaction) {
44
+ // Serialize transaction for API transmission
45
+ const serializedTransaction = this.serializeTransaction(transaction);
46
+ const response = await emblemPost("/sign-solana-transaction", {
47
+ vaultId: this.vaultId,
48
+ transactionToSign: serializedTransaction, // Pass the base64 string directly
49
+ broadcast: false, // Don't broadcast, just sign
50
+ versionedTransaction: true // Match API test format
51
+ }, this.config);
52
+ // Parse the signed transaction response - handle both response formats
53
+ const signedTxData = response.serializedSignedTransaction || response.signedTransaction;
54
+ if (!signedTxData) {
55
+ throw new Error('No signed transaction data received from server');
56
+ }
57
+ return this.deserializeTransaction(signedTxData);
58
+ }
59
+ serializeTransaction(tx) {
60
+ // Handle different transaction formats from @solana/web3.js
61
+ if (tx && typeof tx === 'object') {
62
+ // For VersionedTransaction or Transaction objects
63
+ if (tx.serialize) {
64
+ // Server expects just the base64 string, not an object
65
+ const serialized = tx.serialize();
66
+ const base64 = btoa(String.fromCharCode(...serialized));
67
+ return base64;
68
+ }
69
+ // For transaction objects with instructions
70
+ if (tx.instructions || tx.recentBlockhash) {
71
+ throw new Error('Cannot serialize unsigned transaction objects. Please use VersionedTransaction.');
72
+ }
73
+ }
74
+ // Fallback: assume it's already a string
75
+ return tx;
76
+ }
77
+ deserializeTransaction(signedTxData) {
78
+ // If it's already an object (shouldn't happen based on type), handle it
79
+ if (typeof signedTxData === 'object' && signedTxData.serializedSignedTransaction) {
80
+ signedTxData = signedTxData.serializedSignedTransaction;
81
+ }
82
+ // If it's a string, decode from base64
83
+ if (typeof signedTxData === 'string') {
84
+ try {
85
+ // The server returns base64, so decode it
86
+ const decoded = atob(signedTxData);
87
+ return new Uint8Array(decoded.split('').map(c => c.charCodeAt(0)));
88
+ }
89
+ catch (e) {
90
+ console.error('Failed to decode transaction:', e);
91
+ throw new Error(`Unable to deserialize transaction response: ${e}`);
92
+ }
93
+ }
94
+ // If it's already a Uint8Array or other type, return as-is
95
+ return signedTxData;
96
+ }
97
+ // Additional utility methods for @solana/web3.js compatibility
98
+ /** Get the vault ID for this signer */
99
+ getVaultId() {
100
+ return this.vaultId;
101
+ }
102
+ /** Sign multiple transactions in batch */
103
+ async signAllTransactions(transactions) {
104
+ // Sign each transaction individually for now
105
+ // Could be optimized with a batch API endpoint in the future
106
+ const results = [];
107
+ for (const tx of transactions) {
108
+ results.push(await this.signTransaction(tx));
109
+ }
110
+ return results;
111
+ }
112
+ /** Check if this signer can sign for a given public key */
113
+ canSign(publicKey) {
114
+ return publicKey === this.publicKey;
115
+ }
116
+ /** Sign and optionally broadcast a transaction */
117
+ async signAndBroadcast(transaction, broadcast = true) {
118
+ // Serialize transaction for API transmission
119
+ const serializedTransaction = this.serializeTransaction(transaction);
120
+ const response = await emblemPost("/sign-solana-transaction", {
121
+ vaultId: this.vaultId,
122
+ transactionToSign: serializedTransaction, // Pass the base64 string directly
123
+ broadcast: broadcast,
124
+ versionedTransaction: true // Match API test format
125
+ }, this.config);
126
+ if (broadcast) {
127
+ // Return the transaction signature
128
+ if (!response.transactionSignature) {
129
+ throw new Error('No transaction signature received from broadcast');
130
+ }
131
+ return response.transactionSignature;
132
+ }
133
+ else {
134
+ // Return the signed transaction data
135
+ if (!response.serializedSignedTransaction) {
136
+ throw new Error('No signed transaction data received from server');
137
+ }
138
+ return response.serializedSignedTransaction;
139
+ }
11
140
  }
12
141
  }
13
142
  export async function toSolanaWeb3Signer(config, infoOverride) {
14
143
  const info = infoOverride ?? (await fetchVaultInfo(config));
15
- return new EmblemSolanaSigner(info.address);
144
+ return new EmblemSolanaSigner(config, info);
16
145
  }
17
146
  export async function toSolanaKitSigner(config, infoOverride) {
18
147
  const info = infoOverride ?? (await fetchVaultInfo(config));
19
- return new EmblemSolanaSigner(info.address);
148
+ return new EmblemSolanaSigner(config, info);
20
149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emblem-vault-ai-signers",
3
- "version": "0.1.8-experimental.0",
3
+ "version": "0.1.8-experimental.2",
4
4
  "description": "Emblem Vault remote signer adapters for viem and ethers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -42,10 +42,13 @@
42
42
  "viem": "^2.0.0"
43
43
  },
44
44
  "devDependencies": {
45
- "typescript": "^5.3.3",
46
- "vitest": "^1.6.0",
45
+ "@solana/web3.js": "^1.95.0",
46
+ "bs58": "^6.0.0",
47
47
  "dotenv": "^16.4.5",
48
48
  "ethers": "^6.10.0",
49
- "viem": "^2.0.0"
49
+ "tweetnacl": "^1.0.3",
50
+ "typescript": "^5.3.3",
51
+ "viem": "^2.0.0",
52
+ "vitest": "^1.6.0"
50
53
  }
51
54
  }