emblem-vault-ai-signers 0.1.8-experimental.1 → 0.2.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
@@ -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
 
@@ -21,6 +21,9 @@ npm install emblem-vault-ai-signers
21
21
  # and bring your own peers
22
22
  npm install ethers viem
23
23
 
24
+ # Optional: for Solana support
25
+ npm install @solana/web3.js
26
+
24
27
  # Optional: for SDK integration
25
28
  npm install emblem-auth-sdk
26
29
  ```
@@ -66,11 +69,44 @@ const txHash = await wallet.signAndBroadcast({ to: "0x...", value: 1n }, true);
66
69
  const web3Adapter = await client.toWeb3Adapter();
67
70
  await web3Adapter.signMessage("hello");
68
71
 
69
- // Solana stubs (address only; signing not yet implemented)
72
+ // Solana (@solana/web3.js compatible)
70
73
  const solWeb3 = await client.toSolanaWeb3Signer();
71
- 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
72
108
  const solKit = await client.toSolanaKitSigner();
73
- console.log(solKit.publicKey);
109
+ // Same interface as solWeb3
74
110
  ```
75
111
 
76
112
  ## Authentication
@@ -305,13 +341,43 @@ const provider = new JsonRpcProvider(process.env.RPC_URL!);
305
341
  await provider.broadcastTransaction(rawTransaction);
306
342
  ```
307
343
 
308
- ### Solana (stubs)
344
+ ### Solana (@solana/web3.js)
309
345
 
310
- 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.
311
347
 
312
348
  ```ts
313
- const solWeb3 = await client.toSolanaWeb3Signer();
314
- 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
315
381
  ```
316
382
 
317
383
  ## API
@@ -332,8 +398,8 @@ createEmblemClient(config): EmblemVaultClient
332
398
  EmblemVaultClient#toViemAccount(): Promise<Account>
333
399
  EmblemVaultClient#toEthersWallet(provider?): Promise<Signer>
334
400
  EmblemVaultClient#toWeb3Adapter(): Promise<{ address, signMessage, signTypedData, signTransaction }>
335
- EmblemVaultClient#toSolanaWeb3Signer(): Promise<{ publicKey }>
336
- 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 }>
337
403
 
338
404
  Ethers wallet (v6) adds:
339
405
  - initialize(): Promise<void>
@@ -345,10 +411,15 @@ Ethers wallet (v6) adds:
345
411
 
346
412
  Adapters POST to the Emblem API endpoints:
347
413
 
414
+ **EVM:**
348
415
  - `POST /sign-eth-message` – `{ vaultId, message }`
349
416
  - `POST /sign-typed-message` – `{ vaultId, domain, types, message }`
350
417
  - `POST /sign-eth-tx` – `{ vaultId, transaction }` (expects ethers-serializable fields)
351
418
 
419
+ **Solana:**
420
+ - `POST /sign-solana-message` – `{ vaultId, message }` (base64 encoded)
421
+ - `POST /sign-solana-transaction` – `{ vaultId, transactionToSign, broadcast, versionedTransaction }` (base64 serialized)
422
+
352
423
  On first use, both adapters query `POST /vault/info` with authentication headers to obtain:
353
424
 
354
425
  - Vault ID
@@ -357,6 +428,42 @@ On first use, both adapters query `POST /vault/info` with authentication headers
357
428
 
358
429
  Transactions are normalized to hex/number-like fields before submission.
359
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
+
360
467
  ## Security Considerations
361
468
 
362
469
  ### Client-Side Usage
package/dist/ethers.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import type { EmblemRemoteConfig, VaultInfo } from "./types.js";
2
2
  import { AbstractSigner } from "ethers";
3
- import type { Provider, TransactionLike, TransactionRequest, TransactionResponse, TypedDataDomain, TypedDataField } from "ethers";
3
+ import type { AbstractProvider, TransactionLike, TransactionRequest, TransactionResponse, TypedDataDomain, TypedDataField } from "ethers";
4
4
  export declare class EmblemEthersWallet extends AbstractSigner {
5
5
  private readonly _config;
6
6
  private _address;
7
7
  private _vaultId;
8
8
  private _chainId;
9
9
  private _initPromise?;
10
- constructor(config: EmblemRemoteConfig, provider?: Provider | null, seed?: {
10
+ constructor(config: EmblemRemoteConfig, provider?: AbstractProvider | null, seed?: {
11
11
  address?: `0x${string}`;
12
12
  vaultId?: string;
13
13
  chainId?: number;
@@ -17,7 +17,7 @@ export declare class EmblemEthersWallet extends AbstractSigner {
17
17
  getVaultId(): string;
18
18
  setChainId(chainId: number): void;
19
19
  getChainId(): number;
20
- connect(provider: Provider): EmblemEthersWallet;
20
+ connect(provider: AbstractProvider): EmblemEthersWallet;
21
21
  signMessage(message: string | Uint8Array): Promise<string>;
22
22
  signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
23
23
  _signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
@@ -26,4 +26,4 @@ export declare class EmblemEthersWallet extends AbstractSigner {
26
26
  populateTransaction(transaction: TransactionRequest): Promise<TransactionLike<string>>;
27
27
  signAndBroadcast(transaction: TransactionRequest, waitForReceipt?: boolean): Promise<string>;
28
28
  }
29
- export declare function toEthersWallet(config: EmblemRemoteConfig, provider?: Provider | null, infoOverride?: VaultInfo): Promise<EmblemEthersWallet>;
29
+ export declare function toEthersWallet(config: EmblemRemoteConfig, provider?: AbstractProvider | null, infoOverride?: VaultInfo): Promise<EmblemEthersWallet>;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Provider } from "ethers";
1
+ import type { AbstractProvider } from "ethers";
2
2
  import type { EmblemRemoteConfig } from "./types.js";
3
3
  export type { EmblemRemoteConfig, Hex, VaultInfo } from "./types.js";
4
4
  export type { EmblemSecurityConfig } from "./validation.js";
@@ -31,7 +31,7 @@ export declare class EmblemVaultClient {
31
31
  source: string;
32
32
  type: "local";
33
33
  }>;
34
- toEthersWallet(provider?: Provider | null): Promise<EmblemEthersWallet>;
34
+ toEthersWallet(provider?: AbstractProvider | null): Promise<EmblemEthersWallet>;
35
35
  toSolanaWeb3Signer(): Promise<EmblemSolanaSigner>;
36
36
  toSolanaKitSigner(): Promise<EmblemSolanaSigner>;
37
37
  toWeb3Adapter(): Promise<EmblemWeb3Adapter>;
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.1",
3
+ "version": "0.2.0",
4
4
  "description": "Emblem Vault remote signer adapters for viem and ethers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,19 +33,29 @@
33
33
  "vault",
34
34
  "signer",
35
35
  "ethers",
36
- "viem"
36
+ "viem",
37
+ "solana"
37
38
  ],
38
39
  "author": "",
39
40
  "license": "MIT",
40
41
  "peerDependencies": {
41
42
  "ethers": "^6.10.0",
42
- "viem": "^2.0.0"
43
+ "viem": "^2.0.0",
44
+ "@solana/web3.js": "^1.95.0"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "ethers": { "optional": true },
48
+ "viem": { "optional": true },
49
+ "@solana/web3.js": { "optional": true }
43
50
  },
44
51
  "devDependencies": {
45
- "typescript": "^5.3.3",
46
- "vitest": "^1.6.0",
52
+ "@solana/web3.js": "^1.95.0",
53
+ "bs58": "^6.0.0",
47
54
  "dotenv": "^16.4.5",
48
55
  "ethers": "^6.10.0",
49
- "viem": "^2.0.0"
56
+ "tweetnacl": "^1.0.3",
57
+ "typescript": "^5.3.3",
58
+ "viem": "^2.0.0",
59
+ "vitest": "^1.6.0"
50
60
  }
51
61
  }