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 +296 -24
- package/dist/solana.d.ts +26 -4
- package/dist/solana.js +137 -8
- package/package.json +7 -4
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()` –
|
|
11
|
-
- `toSolanaKitSigner()` –
|
|
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
|
|
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
|
-
|
|
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 (
|
|
344
|
+
### Solana (@solana/web3.js)
|
|
166
345
|
|
|
167
|
-
|
|
346
|
+
Full Solana signing support with message and transaction signing via remote vault.
|
|
168
347
|
|
|
169
348
|
```ts
|
|
170
|
-
const
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
255
|
-
6. **Implement proper error handling** - Don't expose
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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(
|
|
4
|
-
this.publicKey =
|
|
4
|
+
constructor(config, vaultInfo) {
|
|
5
|
+
this.publicKey = vaultInfo.address;
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.vaultId = vaultInfo.vaultId;
|
|
5
8
|
}
|
|
6
|
-
async signMessage(
|
|
7
|
-
|
|
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(
|
|
10
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
46
|
-
"
|
|
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
|
-
"
|
|
49
|
+
"tweetnacl": "^1.0.3",
|
|
50
|
+
"typescript": "^5.3.3",
|
|
51
|
+
"viem": "^2.0.0",
|
|
52
|
+
"vitest": "^1.6.0"
|
|
50
53
|
}
|
|
51
54
|
}
|