@dynamic-labs-wallet/svm 0.0.28
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/index.cjs.d.ts +1 -0
- package/index.cjs.js +306 -0
- package/index.esm.d.ts +1 -0
- package/index.esm.js +304 -0
- package/package.json +31 -0
- package/src/index.d.ts +2 -0
- package/src/index.d.ts.map +1 -0
- package/src/svm/index.d.ts +2 -0
- package/src/svm/index.d.ts.map +1 -0
- package/src/svm/svm.d.ts +96 -0
- package/src/svm/svm.d.ts.map +1 -0
- package/src/svm/utils.d.ts +23 -0
- package/src/svm/utils.d.ts.map +1 -0
package/index.cjs.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/index.cjs.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var browser = require('@dynamic-labs-wallet/browser');
|
|
4
|
+
var bs58 = require('bs58');
|
|
5
|
+
var web3_js = require('@solana/web3.js');
|
|
6
|
+
var core = require('@dynamic-labs-wallet/core');
|
|
7
|
+
|
|
8
|
+
async function getBalance({ address, rpcUrl = core.SOLANA_RPC_URL }) {
|
|
9
|
+
try {
|
|
10
|
+
const connection = new web3_js.Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
11
|
+
const balance = await connection.getBalance(new web3_js.PublicKey(address));
|
|
12
|
+
return balance;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('Error in getting balance:', error);
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function createSolanaTransaction({ senderSolanaAddress, amount, to, rpcUrl = 'https://api.devnet.solana.com' }) {
|
|
19
|
+
try {
|
|
20
|
+
const connection = new web3_js.Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
21
|
+
const balance = await getBalance({
|
|
22
|
+
address: senderSolanaAddress,
|
|
23
|
+
rpcUrl
|
|
24
|
+
});
|
|
25
|
+
if (balance < amount * 1e9) {
|
|
26
|
+
throw new Error('Insufficient balance');
|
|
27
|
+
}
|
|
28
|
+
const fromPubkey = new web3_js.PublicKey(senderSolanaAddress);
|
|
29
|
+
const transaction = new web3_js.Transaction().add(web3_js.SystemProgram.transfer({
|
|
30
|
+
fromPubkey: fromPubkey,
|
|
31
|
+
toPubkey: new web3_js.PublicKey(to),
|
|
32
|
+
lamports: amount * 1e9
|
|
33
|
+
}));
|
|
34
|
+
const { blockhash } = await connection.getLatestBlockhash();
|
|
35
|
+
transaction.recentBlockhash = blockhash;
|
|
36
|
+
transaction.feePayer = fromPubkey;
|
|
37
|
+
const serializedTransaction = transaction.serializeMessage();
|
|
38
|
+
return {
|
|
39
|
+
transaction,
|
|
40
|
+
serializedTransaction
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('Error in creating transaction:', error);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function finalizeTransaction({ transaction, signature }) {
|
|
48
|
+
try {
|
|
49
|
+
transaction.addSignature(transaction.feePayer, Buffer.from(signature));
|
|
50
|
+
return new Uint8Array(transaction.serialize());
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Error in finalizing transaction:', error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function sendTransaction({ signedTransaction, rpcUrl = 'https://api.devnet.solana.com' }) {
|
|
57
|
+
try {
|
|
58
|
+
const connection = new web3_js.Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
59
|
+
const txid = await connection.sendRawTransaction(Buffer.from(signedTransaction));
|
|
60
|
+
return txid;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error in sending transaction:', error);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class DynamicSvmWalletClient extends browser.DynamicWalletClient {
|
|
68
|
+
/**
|
|
69
|
+
* Creates a wallet account on the Solana chain
|
|
70
|
+
*
|
|
71
|
+
* @param thresholdSignatureScheme The threshold signature scheme to use
|
|
72
|
+
* @returns The account address, public key hex, raw public key, and client key shares
|
|
73
|
+
*/ async createWalletAccount({ thresholdSignatureScheme }) {
|
|
74
|
+
try {
|
|
75
|
+
const { rawPublicKey, clientKeyShares } = await this.keyGen({
|
|
76
|
+
chainName: this.chainName,
|
|
77
|
+
thresholdSignatureScheme
|
|
78
|
+
});
|
|
79
|
+
if (!rawPublicKey || !(rawPublicKey instanceof Uint8Array)) {
|
|
80
|
+
throw new Error('Raw public key is not a Uint8Array');
|
|
81
|
+
}
|
|
82
|
+
if (!clientKeyShares) {
|
|
83
|
+
throw new Error('Error creating wallet account');
|
|
84
|
+
}
|
|
85
|
+
// Get EVM address from public key
|
|
86
|
+
const { accountAddress } = await this.deriveAccountAddress(rawPublicKey);
|
|
87
|
+
const refreshedUser = await this.apiClient.refreshUser();
|
|
88
|
+
const newWallet = refreshedUser.user.verifiedCredentials.find((wallet)=>wallet.address === accountAddress);
|
|
89
|
+
console.log('newWallet', newWallet);
|
|
90
|
+
const newWalletId = newWallet.id;
|
|
91
|
+
this.walletMap[accountAddress] = {
|
|
92
|
+
accountAddress,
|
|
93
|
+
walletId: newWalletId,
|
|
94
|
+
chainName: this.chainName,
|
|
95
|
+
clientKeyShares: clientKeyShares,
|
|
96
|
+
thresholdSignatureScheme
|
|
97
|
+
};
|
|
98
|
+
await this.storeEncryptedBackupByWallet({
|
|
99
|
+
accountAddress,
|
|
100
|
+
password: undefined
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
accountAddress,
|
|
104
|
+
rawPublicKey: rawPublicKey,
|
|
105
|
+
clientKeyShares
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(`Error creating ${this.chainName} wallet account`, error);
|
|
109
|
+
throw new Error(`Error creating ${this.chainName} wallet account`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Function to properly derive account address
|
|
113
|
+
async deriveAccountAddress(rawPublicKey) {
|
|
114
|
+
const accountAddress = bs58.encode(rawPublicKey);
|
|
115
|
+
return {
|
|
116
|
+
accountAddress
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* This function takes a message and returns it after being signed with MPC
|
|
121
|
+
*
|
|
122
|
+
* @param message The message to sign (Uint8Array)
|
|
123
|
+
* @param fromAddress Solana address (base58 encoded)
|
|
124
|
+
*/ async signMessage({ message, accountAddress }) {
|
|
125
|
+
if (!accountAddress) {
|
|
126
|
+
throw new Error('Account address is required');
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const signatureEd25519 = await this.sign({
|
|
130
|
+
message,
|
|
131
|
+
accountAddress: accountAddress,
|
|
132
|
+
chainName: this.chainName
|
|
133
|
+
});
|
|
134
|
+
const base58Signature = bs58.encode(signatureEd25519);
|
|
135
|
+
return base58Signature;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Error signing message:', error);
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// async verifyMessageSignature({
|
|
142
|
+
// accountAddress,
|
|
143
|
+
// message,
|
|
144
|
+
// signature,
|
|
145
|
+
// }: {
|
|
146
|
+
// accountAddress: string;
|
|
147
|
+
// message: string;
|
|
148
|
+
// signature: string;
|
|
149
|
+
// }) {}
|
|
150
|
+
async signTransaction({ senderAddress, toAddress, value, rpcUrl = `https://api.devnet.solana.com` }) {
|
|
151
|
+
try {
|
|
152
|
+
const { transaction, serializedTransaction } = await createSolanaTransaction({
|
|
153
|
+
senderSolanaAddress: senderAddress,
|
|
154
|
+
amount: value,
|
|
155
|
+
to: toAddress,
|
|
156
|
+
rpcUrl
|
|
157
|
+
});
|
|
158
|
+
if (!serializedTransaction || !transaction) {
|
|
159
|
+
throw new Error('Serialized transaction is undefined');
|
|
160
|
+
}
|
|
161
|
+
const rawMessageToSign = new Uint8Array(serializedTransaction);
|
|
162
|
+
//convert raw bites to hex to send to server
|
|
163
|
+
const messageToSign = Buffer.from(rawMessageToSign).toString('hex');
|
|
164
|
+
const signatureEd25519 = await this.sign({
|
|
165
|
+
message: messageToSign,
|
|
166
|
+
accountAddress: senderAddress,
|
|
167
|
+
chainName: this.chainName
|
|
168
|
+
});
|
|
169
|
+
if (!signatureEd25519) {
|
|
170
|
+
throw new Error('Signature is undefined');
|
|
171
|
+
}
|
|
172
|
+
const signedTransaction = finalizeTransaction({
|
|
173
|
+
transaction,
|
|
174
|
+
signature: signatureEd25519
|
|
175
|
+
});
|
|
176
|
+
if (!signedTransaction) {
|
|
177
|
+
throw new Error('Signed transaction is undefined');
|
|
178
|
+
}
|
|
179
|
+
const txid = await sendTransaction({
|
|
180
|
+
signedTransaction,
|
|
181
|
+
rpcUrl
|
|
182
|
+
});
|
|
183
|
+
if (!txid) {
|
|
184
|
+
throw new Error('Transaction hash is undefined');
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
txHash: txid,
|
|
188
|
+
signedTx: serializedTransaction
|
|
189
|
+
};
|
|
190
|
+
} catch (error) {
|
|
191
|
+
this.logger.error('Error in signing and broadcasting transaction:', error);
|
|
192
|
+
if (error instanceof Error) {
|
|
193
|
+
this.logger.error('Error details:', error);
|
|
194
|
+
}
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Exports the private key for a given account address
|
|
200
|
+
*
|
|
201
|
+
* @param accountAddress The account address to export the private key for
|
|
202
|
+
* @returns The private key
|
|
203
|
+
*/ async exportPrivateKey({ accountAddress }) {
|
|
204
|
+
const { derivedPrivateKey } = await this.exportKey({
|
|
205
|
+
accountAddress,
|
|
206
|
+
chainName: this.chainName
|
|
207
|
+
});
|
|
208
|
+
if (!derivedPrivateKey) {
|
|
209
|
+
throw new Error('Derived private key is undefined');
|
|
210
|
+
}
|
|
211
|
+
const encodedPrivateKey = bs58.encode(Buffer.from(derivedPrivateKey));
|
|
212
|
+
return {
|
|
213
|
+
derivedPrivateKey: encodedPrivateKey
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Exports the private key for a given account address
|
|
218
|
+
*
|
|
219
|
+
* @param keyShares The key shares to export the private key for
|
|
220
|
+
* @returns The private key
|
|
221
|
+
*/ async offlineExportPrivateKey({ keyShares }) {
|
|
222
|
+
const { derivedPrivateKey } = await this.offlineExportKey({
|
|
223
|
+
chainName: this.chainName,
|
|
224
|
+
keyShares
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
derivedPrivateKey
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Converts the private key to a hex string
|
|
232
|
+
*
|
|
233
|
+
* @param privateKey The private key to convert
|
|
234
|
+
* @returns The hex string
|
|
235
|
+
*/ decodePrivateKeyForSolana(privateKey) {
|
|
236
|
+
const decoded = bs58.decode(privateKey);
|
|
237
|
+
const slicedBytes = decoded.slice(0, 32);
|
|
238
|
+
return Buffer.from(slicedBytes).toString('hex');
|
|
239
|
+
}
|
|
240
|
+
getPublicKeyFromPrivateKey(privateKey) {
|
|
241
|
+
const privateKeyBytes = bs58.decode(privateKey);
|
|
242
|
+
const keypair = web3_js.Keypair.fromSecretKey(privateKeyBytes);
|
|
243
|
+
const publicKeyBase58 = keypair.publicKey.toBase58();
|
|
244
|
+
return publicKeyBase58;
|
|
245
|
+
}
|
|
246
|
+
encodePublicKey(publicKey) {
|
|
247
|
+
return bs58.encode(publicKey);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Imports the private key for a given account address
|
|
251
|
+
*
|
|
252
|
+
* @param privateKey The private key to import
|
|
253
|
+
* @param chainName The chain name to import the private key for
|
|
254
|
+
* @param thresholdSignatureScheme The threshold signature scheme to use
|
|
255
|
+
* @returns The account address, raw public key, and client key shares
|
|
256
|
+
*/ async importPrivateKey({ privateKey, chainName, thresholdSignatureScheme }) {
|
|
257
|
+
//get public key from private key
|
|
258
|
+
const publicKey = this.getPublicKeyFromPrivateKey(privateKey);
|
|
259
|
+
const formattedPrivateKey = await this.decodePrivateKeyForSolana(privateKey);
|
|
260
|
+
const { rawPublicKey, clientKeyShares } = await this.importRawPrivateKey({
|
|
261
|
+
chainName,
|
|
262
|
+
privateKey: formattedPrivateKey,
|
|
263
|
+
thresholdSignatureScheme
|
|
264
|
+
});
|
|
265
|
+
if (!rawPublicKey || !clientKeyShares) {
|
|
266
|
+
throw new Error('Error creating wallet account');
|
|
267
|
+
}
|
|
268
|
+
const { accountAddress } = await this.deriveAccountAddress(rawPublicKey);
|
|
269
|
+
if (accountAddress !== publicKey) {
|
|
270
|
+
throw new Error(`Public key mismatch: derived address ${accountAddress} !== public key ${publicKey}`);
|
|
271
|
+
}
|
|
272
|
+
const refreshedUser = await this.apiClient.refreshUser();
|
|
273
|
+
const newWallet = refreshedUser.user.verifiedCredentials.find((wallet)=>wallet.address === accountAddress);
|
|
274
|
+
const newWalletId = newWallet.id;
|
|
275
|
+
this.walletMap[accountAddress] = {
|
|
276
|
+
accountAddress,
|
|
277
|
+
walletId: newWalletId,
|
|
278
|
+
chainName: this.chainName,
|
|
279
|
+
clientKeyShares,
|
|
280
|
+
thresholdSignatureScheme
|
|
281
|
+
};
|
|
282
|
+
await this.storeEncryptedBackupByWallet({
|
|
283
|
+
accountAddress
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
accountAddress,
|
|
287
|
+
rawPublicKey: rawPublicKey,
|
|
288
|
+
clientKeyShares
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
async getSvmWallets() {
|
|
292
|
+
const wallets = await this.getWallets();
|
|
293
|
+
const svmWallets = wallets.filter((wallet)=>wallet.chainName === 'solana');
|
|
294
|
+
return svmWallets;
|
|
295
|
+
}
|
|
296
|
+
constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl }){
|
|
297
|
+
super({
|
|
298
|
+
environmentId,
|
|
299
|
+
authToken,
|
|
300
|
+
baseApiUrl,
|
|
301
|
+
baseMPCRelayApiUrl
|
|
302
|
+
}), this.chainName = 'SOL';
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
exports.DynamicSvmWalletClient = DynamicSvmWalletClient;
|
package/index.esm.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/index.esm.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { DynamicWalletClient } from '@dynamic-labs-wallet/browser';
|
|
2
|
+
import bs58 from 'bs58';
|
|
3
|
+
import { Connection, PublicKey, Transaction, SystemProgram, Keypair } from '@solana/web3.js';
|
|
4
|
+
import { SOLANA_RPC_URL } from '@dynamic-labs-wallet/core';
|
|
5
|
+
|
|
6
|
+
async function getBalance({ address, rpcUrl = SOLANA_RPC_URL }) {
|
|
7
|
+
try {
|
|
8
|
+
const connection = new Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
9
|
+
const balance = await connection.getBalance(new PublicKey(address));
|
|
10
|
+
return balance;
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error('Error in getting balance:', error);
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function createSolanaTransaction({ senderSolanaAddress, amount, to, rpcUrl = 'https://api.devnet.solana.com' }) {
|
|
17
|
+
try {
|
|
18
|
+
const connection = new Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
19
|
+
const balance = await getBalance({
|
|
20
|
+
address: senderSolanaAddress,
|
|
21
|
+
rpcUrl
|
|
22
|
+
});
|
|
23
|
+
if (balance < amount * 1e9) {
|
|
24
|
+
throw new Error('Insufficient balance');
|
|
25
|
+
}
|
|
26
|
+
const fromPubkey = new PublicKey(senderSolanaAddress);
|
|
27
|
+
const transaction = new Transaction().add(SystemProgram.transfer({
|
|
28
|
+
fromPubkey: fromPubkey,
|
|
29
|
+
toPubkey: new PublicKey(to),
|
|
30
|
+
lamports: amount * 1e9
|
|
31
|
+
}));
|
|
32
|
+
const { blockhash } = await connection.getLatestBlockhash();
|
|
33
|
+
transaction.recentBlockhash = blockhash;
|
|
34
|
+
transaction.feePayer = fromPubkey;
|
|
35
|
+
const serializedTransaction = transaction.serializeMessage();
|
|
36
|
+
return {
|
|
37
|
+
transaction,
|
|
38
|
+
serializedTransaction
|
|
39
|
+
};
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Error in creating transaction:', error);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function finalizeTransaction({ transaction, signature }) {
|
|
46
|
+
try {
|
|
47
|
+
transaction.addSignature(transaction.feePayer, Buffer.from(signature));
|
|
48
|
+
return new Uint8Array(transaction.serialize());
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Error in finalizing transaction:', error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function sendTransaction({ signedTransaction, rpcUrl = 'https://api.devnet.solana.com' }) {
|
|
55
|
+
try {
|
|
56
|
+
const connection = new Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
57
|
+
const txid = await connection.sendRawTransaction(Buffer.from(signedTransaction));
|
|
58
|
+
return txid;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Error in sending transaction:', error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class DynamicSvmWalletClient extends DynamicWalletClient {
|
|
66
|
+
/**
|
|
67
|
+
* Creates a wallet account on the Solana chain
|
|
68
|
+
*
|
|
69
|
+
* @param thresholdSignatureScheme The threshold signature scheme to use
|
|
70
|
+
* @returns The account address, public key hex, raw public key, and client key shares
|
|
71
|
+
*/ async createWalletAccount({ thresholdSignatureScheme }) {
|
|
72
|
+
try {
|
|
73
|
+
const { rawPublicKey, clientKeyShares } = await this.keyGen({
|
|
74
|
+
chainName: this.chainName,
|
|
75
|
+
thresholdSignatureScheme
|
|
76
|
+
});
|
|
77
|
+
if (!rawPublicKey || !(rawPublicKey instanceof Uint8Array)) {
|
|
78
|
+
throw new Error('Raw public key is not a Uint8Array');
|
|
79
|
+
}
|
|
80
|
+
if (!clientKeyShares) {
|
|
81
|
+
throw new Error('Error creating wallet account');
|
|
82
|
+
}
|
|
83
|
+
// Get EVM address from public key
|
|
84
|
+
const { accountAddress } = await this.deriveAccountAddress(rawPublicKey);
|
|
85
|
+
const refreshedUser = await this.apiClient.refreshUser();
|
|
86
|
+
const newWallet = refreshedUser.user.verifiedCredentials.find((wallet)=>wallet.address === accountAddress);
|
|
87
|
+
console.log('newWallet', newWallet);
|
|
88
|
+
const newWalletId = newWallet.id;
|
|
89
|
+
this.walletMap[accountAddress] = {
|
|
90
|
+
accountAddress,
|
|
91
|
+
walletId: newWalletId,
|
|
92
|
+
chainName: this.chainName,
|
|
93
|
+
clientKeyShares: clientKeyShares,
|
|
94
|
+
thresholdSignatureScheme
|
|
95
|
+
};
|
|
96
|
+
await this.storeEncryptedBackupByWallet({
|
|
97
|
+
accountAddress,
|
|
98
|
+
password: undefined
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
accountAddress,
|
|
102
|
+
rawPublicKey: rawPublicKey,
|
|
103
|
+
clientKeyShares
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(`Error creating ${this.chainName} wallet account`, error);
|
|
107
|
+
throw new Error(`Error creating ${this.chainName} wallet account`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Function to properly derive account address
|
|
111
|
+
async deriveAccountAddress(rawPublicKey) {
|
|
112
|
+
const accountAddress = bs58.encode(rawPublicKey);
|
|
113
|
+
return {
|
|
114
|
+
accountAddress
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* This function takes a message and returns it after being signed with MPC
|
|
119
|
+
*
|
|
120
|
+
* @param message The message to sign (Uint8Array)
|
|
121
|
+
* @param fromAddress Solana address (base58 encoded)
|
|
122
|
+
*/ async signMessage({ message, accountAddress }) {
|
|
123
|
+
if (!accountAddress) {
|
|
124
|
+
throw new Error('Account address is required');
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const signatureEd25519 = await this.sign({
|
|
128
|
+
message,
|
|
129
|
+
accountAddress: accountAddress,
|
|
130
|
+
chainName: this.chainName
|
|
131
|
+
});
|
|
132
|
+
const base58Signature = bs58.encode(signatureEd25519);
|
|
133
|
+
return base58Signature;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error('Error signing message:', error);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// async verifyMessageSignature({
|
|
140
|
+
// accountAddress,
|
|
141
|
+
// message,
|
|
142
|
+
// signature,
|
|
143
|
+
// }: {
|
|
144
|
+
// accountAddress: string;
|
|
145
|
+
// message: string;
|
|
146
|
+
// signature: string;
|
|
147
|
+
// }) {}
|
|
148
|
+
async signTransaction({ senderAddress, toAddress, value, rpcUrl = `https://api.devnet.solana.com` }) {
|
|
149
|
+
try {
|
|
150
|
+
const { transaction, serializedTransaction } = await createSolanaTransaction({
|
|
151
|
+
senderSolanaAddress: senderAddress,
|
|
152
|
+
amount: value,
|
|
153
|
+
to: toAddress,
|
|
154
|
+
rpcUrl
|
|
155
|
+
});
|
|
156
|
+
if (!serializedTransaction || !transaction) {
|
|
157
|
+
throw new Error('Serialized transaction is undefined');
|
|
158
|
+
}
|
|
159
|
+
const rawMessageToSign = new Uint8Array(serializedTransaction);
|
|
160
|
+
//convert raw bites to hex to send to server
|
|
161
|
+
const messageToSign = Buffer.from(rawMessageToSign).toString('hex');
|
|
162
|
+
const signatureEd25519 = await this.sign({
|
|
163
|
+
message: messageToSign,
|
|
164
|
+
accountAddress: senderAddress,
|
|
165
|
+
chainName: this.chainName
|
|
166
|
+
});
|
|
167
|
+
if (!signatureEd25519) {
|
|
168
|
+
throw new Error('Signature is undefined');
|
|
169
|
+
}
|
|
170
|
+
const signedTransaction = finalizeTransaction({
|
|
171
|
+
transaction,
|
|
172
|
+
signature: signatureEd25519
|
|
173
|
+
});
|
|
174
|
+
if (!signedTransaction) {
|
|
175
|
+
throw new Error('Signed transaction is undefined');
|
|
176
|
+
}
|
|
177
|
+
const txid = await sendTransaction({
|
|
178
|
+
signedTransaction,
|
|
179
|
+
rpcUrl
|
|
180
|
+
});
|
|
181
|
+
if (!txid) {
|
|
182
|
+
throw new Error('Transaction hash is undefined');
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
txHash: txid,
|
|
186
|
+
signedTx: serializedTransaction
|
|
187
|
+
};
|
|
188
|
+
} catch (error) {
|
|
189
|
+
this.logger.error('Error in signing and broadcasting transaction:', error);
|
|
190
|
+
if (error instanceof Error) {
|
|
191
|
+
this.logger.error('Error details:', error);
|
|
192
|
+
}
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Exports the private key for a given account address
|
|
198
|
+
*
|
|
199
|
+
* @param accountAddress The account address to export the private key for
|
|
200
|
+
* @returns The private key
|
|
201
|
+
*/ async exportPrivateKey({ accountAddress }) {
|
|
202
|
+
const { derivedPrivateKey } = await this.exportKey({
|
|
203
|
+
accountAddress,
|
|
204
|
+
chainName: this.chainName
|
|
205
|
+
});
|
|
206
|
+
if (!derivedPrivateKey) {
|
|
207
|
+
throw new Error('Derived private key is undefined');
|
|
208
|
+
}
|
|
209
|
+
const encodedPrivateKey = bs58.encode(Buffer.from(derivedPrivateKey));
|
|
210
|
+
return {
|
|
211
|
+
derivedPrivateKey: encodedPrivateKey
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Exports the private key for a given account address
|
|
216
|
+
*
|
|
217
|
+
* @param keyShares The key shares to export the private key for
|
|
218
|
+
* @returns The private key
|
|
219
|
+
*/ async offlineExportPrivateKey({ keyShares }) {
|
|
220
|
+
const { derivedPrivateKey } = await this.offlineExportKey({
|
|
221
|
+
chainName: this.chainName,
|
|
222
|
+
keyShares
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
derivedPrivateKey
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Converts the private key to a hex string
|
|
230
|
+
*
|
|
231
|
+
* @param privateKey The private key to convert
|
|
232
|
+
* @returns The hex string
|
|
233
|
+
*/ decodePrivateKeyForSolana(privateKey) {
|
|
234
|
+
const decoded = bs58.decode(privateKey);
|
|
235
|
+
const slicedBytes = decoded.slice(0, 32);
|
|
236
|
+
return Buffer.from(slicedBytes).toString('hex');
|
|
237
|
+
}
|
|
238
|
+
getPublicKeyFromPrivateKey(privateKey) {
|
|
239
|
+
const privateKeyBytes = bs58.decode(privateKey);
|
|
240
|
+
const keypair = Keypair.fromSecretKey(privateKeyBytes);
|
|
241
|
+
const publicKeyBase58 = keypair.publicKey.toBase58();
|
|
242
|
+
return publicKeyBase58;
|
|
243
|
+
}
|
|
244
|
+
encodePublicKey(publicKey) {
|
|
245
|
+
return bs58.encode(publicKey);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Imports the private key for a given account address
|
|
249
|
+
*
|
|
250
|
+
* @param privateKey The private key to import
|
|
251
|
+
* @param chainName The chain name to import the private key for
|
|
252
|
+
* @param thresholdSignatureScheme The threshold signature scheme to use
|
|
253
|
+
* @returns The account address, raw public key, and client key shares
|
|
254
|
+
*/ async importPrivateKey({ privateKey, chainName, thresholdSignatureScheme }) {
|
|
255
|
+
//get public key from private key
|
|
256
|
+
const publicKey = this.getPublicKeyFromPrivateKey(privateKey);
|
|
257
|
+
const formattedPrivateKey = await this.decodePrivateKeyForSolana(privateKey);
|
|
258
|
+
const { rawPublicKey, clientKeyShares } = await this.importRawPrivateKey({
|
|
259
|
+
chainName,
|
|
260
|
+
privateKey: formattedPrivateKey,
|
|
261
|
+
thresholdSignatureScheme
|
|
262
|
+
});
|
|
263
|
+
if (!rawPublicKey || !clientKeyShares) {
|
|
264
|
+
throw new Error('Error creating wallet account');
|
|
265
|
+
}
|
|
266
|
+
const { accountAddress } = await this.deriveAccountAddress(rawPublicKey);
|
|
267
|
+
if (accountAddress !== publicKey) {
|
|
268
|
+
throw new Error(`Public key mismatch: derived address ${accountAddress} !== public key ${publicKey}`);
|
|
269
|
+
}
|
|
270
|
+
const refreshedUser = await this.apiClient.refreshUser();
|
|
271
|
+
const newWallet = refreshedUser.user.verifiedCredentials.find((wallet)=>wallet.address === accountAddress);
|
|
272
|
+
const newWalletId = newWallet.id;
|
|
273
|
+
this.walletMap[accountAddress] = {
|
|
274
|
+
accountAddress,
|
|
275
|
+
walletId: newWalletId,
|
|
276
|
+
chainName: this.chainName,
|
|
277
|
+
clientKeyShares,
|
|
278
|
+
thresholdSignatureScheme
|
|
279
|
+
};
|
|
280
|
+
await this.storeEncryptedBackupByWallet({
|
|
281
|
+
accountAddress
|
|
282
|
+
});
|
|
283
|
+
return {
|
|
284
|
+
accountAddress,
|
|
285
|
+
rawPublicKey: rawPublicKey,
|
|
286
|
+
clientKeyShares
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
async getSvmWallets() {
|
|
290
|
+
const wallets = await this.getWallets();
|
|
291
|
+
const svmWallets = wallets.filter((wallet)=>wallet.chainName === 'solana');
|
|
292
|
+
return svmWallets;
|
|
293
|
+
}
|
|
294
|
+
constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl }){
|
|
295
|
+
super({
|
|
296
|
+
environmentId,
|
|
297
|
+
authToken,
|
|
298
|
+
baseApiUrl,
|
|
299
|
+
baseMPCRelayApiUrl
|
|
300
|
+
}), this.chainName = 'SOL';
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export { DynamicSvmWalletClient };
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dynamic-labs-wallet/svm",
|
|
3
|
+
"version": "0.0.28",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"@dynamic-labs-wallet/browser": "0.0.28",
|
|
6
|
+
"@dynamic-labs-wallet/core": "0.0.28",
|
|
7
|
+
"@solana/web3.js": "^1.98.0",
|
|
8
|
+
"bs58": "^6.0.0"
|
|
9
|
+
},
|
|
10
|
+
"nx": {
|
|
11
|
+
"sourceRoot": "packages/svm/src",
|
|
12
|
+
"projectType": "library",
|
|
13
|
+
"name": "svm",
|
|
14
|
+
"targets": {
|
|
15
|
+
"build": {}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./index.cjs.js",
|
|
20
|
+
"module": "./index.esm.js",
|
|
21
|
+
"types": "./index.esm.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
"./package.json": "./package.json",
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./index.esm.d.ts",
|
|
26
|
+
"import": "./index.esm.js",
|
|
27
|
+
"require": "./index.cjs.js",
|
|
28
|
+
"default": "./index.cjs.js"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../packages/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svm/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC"}
|
package/src/svm/svm.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ClientKeyShare, DynamicWalletClient, Ed25519KeygenResult, ThresholdSignatureScheme } from '@dynamic-labs-wallet/browser';
|
|
2
|
+
export declare class DynamicSvmWalletClient extends DynamicWalletClient {
|
|
3
|
+
readonly chainName = "SOL";
|
|
4
|
+
accountAddress?: string;
|
|
5
|
+
constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, }: {
|
|
6
|
+
environmentId: string;
|
|
7
|
+
authToken: string;
|
|
8
|
+
baseApiUrl?: string;
|
|
9
|
+
baseMPCRelayApiUrl?: string;
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Creates a wallet account on the Solana chain
|
|
13
|
+
*
|
|
14
|
+
* @param thresholdSignatureScheme The threshold signature scheme to use
|
|
15
|
+
* @returns The account address, public key hex, raw public key, and client key shares
|
|
16
|
+
*/
|
|
17
|
+
createWalletAccount({ thresholdSignatureScheme, }: {
|
|
18
|
+
thresholdSignatureScheme: ThresholdSignatureScheme;
|
|
19
|
+
}): Promise<{
|
|
20
|
+
accountAddress: string;
|
|
21
|
+
rawPublicKey: Uint8Array;
|
|
22
|
+
clientKeyShares: ClientKeyShare[];
|
|
23
|
+
}>;
|
|
24
|
+
deriveAccountAddress(rawPublicKey: Uint8Array): Promise<{
|
|
25
|
+
accountAddress: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* This function takes a message and returns it after being signed with MPC
|
|
29
|
+
*
|
|
30
|
+
* @param message The message to sign (Uint8Array)
|
|
31
|
+
* @param fromAddress Solana address (base58 encoded)
|
|
32
|
+
*/
|
|
33
|
+
signMessage({ message, accountAddress, }: {
|
|
34
|
+
message: string;
|
|
35
|
+
accountAddress?: string;
|
|
36
|
+
}): Promise<string>;
|
|
37
|
+
signTransaction({ senderAddress, toAddress, value, rpcUrl, }: {
|
|
38
|
+
senderAddress: string;
|
|
39
|
+
toAddress: string;
|
|
40
|
+
value: number;
|
|
41
|
+
rpcUrl: string;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
txHash?: string;
|
|
44
|
+
signedTx: Uint8Array;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Exports the private key for a given account address
|
|
48
|
+
*
|
|
49
|
+
* @param accountAddress The account address to export the private key for
|
|
50
|
+
* @returns The private key
|
|
51
|
+
*/
|
|
52
|
+
exportPrivateKey({ accountAddress }: {
|
|
53
|
+
accountAddress: string;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
derivedPrivateKey: string;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Exports the private key for a given account address
|
|
59
|
+
*
|
|
60
|
+
* @param keyShares The key shares to export the private key for
|
|
61
|
+
* @returns The private key
|
|
62
|
+
*/
|
|
63
|
+
offlineExportPrivateKey({ keyShares, }: {
|
|
64
|
+
keyShares: Ed25519KeygenResult[];
|
|
65
|
+
}): Promise<{
|
|
66
|
+
derivedPrivateKey: string | undefined;
|
|
67
|
+
}>;
|
|
68
|
+
/**
|
|
69
|
+
* Converts the private key to a hex string
|
|
70
|
+
*
|
|
71
|
+
* @param privateKey The private key to convert
|
|
72
|
+
* @returns The hex string
|
|
73
|
+
*/
|
|
74
|
+
decodePrivateKeyForSolana(privateKey: string): string;
|
|
75
|
+
getPublicKeyFromPrivateKey(privateKey: string): string;
|
|
76
|
+
encodePublicKey(publicKey: Uint8Array): string;
|
|
77
|
+
/**
|
|
78
|
+
* Imports the private key for a given account address
|
|
79
|
+
*
|
|
80
|
+
* @param privateKey The private key to import
|
|
81
|
+
* @param chainName The chain name to import the private key for
|
|
82
|
+
* @param thresholdSignatureScheme The threshold signature scheme to use
|
|
83
|
+
* @returns The account address, raw public key, and client key shares
|
|
84
|
+
*/
|
|
85
|
+
importPrivateKey({ privateKey, chainName, thresholdSignatureScheme, }: {
|
|
86
|
+
privateKey: string;
|
|
87
|
+
chainName: string;
|
|
88
|
+
thresholdSignatureScheme: ThresholdSignatureScheme;
|
|
89
|
+
}): Promise<{
|
|
90
|
+
accountAddress: string;
|
|
91
|
+
rawPublicKey: Uint8Array | undefined;
|
|
92
|
+
clientKeyShares: ClientKeyShare[];
|
|
93
|
+
}>;
|
|
94
|
+
getSvmWallets(): Promise<any>;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=svm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svm.d.ts","sourceRoot":"","sources":["../../src/svm/svm.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,8BAA8B,CAAC;AAStC,qBAAa,sBAAuB,SAAQ,mBAAmB;IAC7D,QAAQ,CAAC,SAAS,SAAS;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;gBAEZ,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACV,kBAAkB,GACnB,EAAE;QACD,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B;IASD;;;;;OAKG;IACG,mBAAmB,CAAC,EACxB,wBAAwB,GACzB,EAAE;QACD,wBAAwB,EAAE,wBAAwB,CAAC;KACpD,GAAG,OAAO,CAAC;QACV,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,UAAU,CAAC;QACzB,eAAe,EAAE,cAAc,EAAE,CAAC;KACnC,CAAC;IAmDI,oBAAoB,CAAC,YAAY,EAAE,UAAU;;;IAOnD;;;;;OAKG;IACG,WAAW,CAAC,EAChB,OAAO,EACP,cAAc,GACf,EAAE;QACD,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB;IA8BK,eAAe,CAAC,EACpB,aAAa,EACb,SAAS,EACT,KAAK,EACL,MAAwC,GACzC,EAAE;QACD,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,UAAU,CAAC;KACtB,CAAC;IA0DF;;;;;OAKG;IACG,gBAAgB,CAAC,EAAE,cAAc,EAAE,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE;;;IAYrE;;;;;OAKG;IACG,uBAAuB,CAAC,EAC5B,SAAS,GACV,EAAE;QACD,SAAS,EAAE,mBAAmB,EAAE,CAAC;KAClC;;;IAQD;;;;;OAKG;IACH,yBAAyB,CAAC,UAAU,EAAE,MAAM;IAM5C,0BAA0B,CAAC,UAAU,EAAE,MAAM;IAQ7C,eAAe,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM;IAI9C;;;;;;;OAOG;IACG,gBAAgB,CAAC,EACrB,UAAU,EACV,SAAS,EACT,wBAAwB,GACzB,EAAE;QACD,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,wBAAwB,EAAE,wBAAwB,CAAC;KACpD,GAAG,OAAO,CAAC;QACV,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,UAAU,GAAG,SAAS,CAAC;QACrC,eAAe,EAAE,cAAc,EAAE,CAAC;KACnC,CAAC;IAkDI,aAAa;CAOpB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Transaction } from '@solana/web3.js';
|
|
2
|
+
export declare function getBalance({ address, rpcUrl, }: {
|
|
3
|
+
address: string;
|
|
4
|
+
rpcUrl?: string;
|
|
5
|
+
}): Promise<number>;
|
|
6
|
+
export declare function createSolanaTransaction({ senderSolanaAddress, amount, to, rpcUrl, }: {
|
|
7
|
+
senderSolanaAddress: string;
|
|
8
|
+
amount: number;
|
|
9
|
+
to: string;
|
|
10
|
+
rpcUrl?: string;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
transaction: Transaction;
|
|
13
|
+
serializedTransaction: Buffer;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function finalizeTransaction({ transaction, signature, }: {
|
|
16
|
+
transaction: Transaction;
|
|
17
|
+
signature: Uint8Array;
|
|
18
|
+
}): Uint8Array;
|
|
19
|
+
export declare function sendTransaction({ signedTransaction, rpcUrl, }: {
|
|
20
|
+
signedTransaction: Uint8Array;
|
|
21
|
+
rpcUrl?: string;
|
|
22
|
+
}): Promise<string>;
|
|
23
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/svm/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAEzB,wBAAsB,UAAU,CAAC,EAC/B,OAAO,EACP,MAAuB,GACxB,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,mBAWA;AAED,wBAAsB,uBAAuB,CAAC,EAC5C,mBAAmB,EACnB,MAAM,EACN,EAAE,EACF,MAAwC,GACzC,EAAE;IACD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;;;GA+BA;AAED,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,SAAS,GACV,EAAE;IACD,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,UAAU,CAAC;CACvB,cAWA;AAED,wBAAsB,eAAe,CAAC,EACpC,iBAAiB,EACjB,MAAwC,GACzC,EAAE;IACD,iBAAiB,EAAE,UAAU,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,mBAaA"}
|