@dynamic-labs-wallet/node-svm 0.0.0-preview.160.0 → 0.0.1-paolo-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +405 -97
- package/index.esm.js +400 -100
- package/package.json +11 -5
- package/src/client/base58.d.ts +3 -0
- package/src/client/base58.d.ts.map +1 -0
- package/src/client/client.d.ts +21 -23
- package/src/client/client.d.ts.map +1 -1
- package/src/client/index.d.ts +4 -2
- package/src/client/index.d.ts.map +1 -1
- package/src/client/utils.d.ts +6 -1
- package/src/client/utils.d.ts.map +1 -1
- package/src/delegatedClient.d.ts +79 -0
- package/src/delegatedClient.d.ts.map +1 -0
- package/src/index.d.ts +2 -1
- package/src/index.d.ts.map +1 -1
- package/src/services/logger.d.ts +10 -0
- package/src/services/logger.d.ts.map +1 -0
package/index.cjs.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var node = require('@dynamic-labs-wallet/node');
|
|
4
4
|
var web3_js = require('@solana/web3.js');
|
|
5
|
+
var logger$1 = require('@dynamic-labs/logger');
|
|
6
|
+
var axios = require('axios');
|
|
7
|
+
var core = require('@dynamic-labs-wallet/core');
|
|
5
8
|
|
|
6
9
|
function _extends() {
|
|
7
10
|
_extends = Object.assign || function assign(target) {
|
|
@@ -16,95 +19,163 @@ function _extends() {
|
|
|
16
19
|
|
|
17
20
|
const ERROR_CREATE_WALLET_ACCOUNT = 'Error creating svm wallet account';
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// Base58 encoding/decoding (Bitcoin alphabet) without external dependencies
|
|
23
|
+
// Implementation adapted from the reference algorithm; suitable for keys/signatures
|
|
24
|
+
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
25
|
+
const ALPHABET_MAP = {};
|
|
26
|
+
for(let i = 0; i < ALPHABET.length; i++)ALPHABET_MAP[ALPHABET[i]] = i;
|
|
27
|
+
function encodeBase58(source) {
|
|
28
|
+
const bytes = source instanceof Uint8Array ? source : new Uint8Array(source);
|
|
29
|
+
if (bytes.length === 0) return '';
|
|
30
|
+
// Count leading zeros
|
|
31
|
+
let zeros = 0;
|
|
32
|
+
while(zeros < bytes.length && bytes[zeros] === 0)zeros++;
|
|
33
|
+
// Allocate enough space in big-endian base58 representation.
|
|
34
|
+
// log(256) / log(58), rounded up.
|
|
35
|
+
const size = (bytes.length - zeros) * 138 / 100 + 1 >>> 0;
|
|
36
|
+
const b58 = new Uint8Array(size);
|
|
37
|
+
let length = 0;
|
|
38
|
+
for(let i = zeros; i < bytes.length; i++){
|
|
39
|
+
let carry = bytes[i];
|
|
40
|
+
let j = 0;
|
|
41
|
+
for(let k = size - 1; (carry !== 0 || j < length) && k >= 0; k--, j++){
|
|
42
|
+
carry += 256 * b58[k] >>> 0;
|
|
43
|
+
b58[k] = carry % 58 >>> 0;
|
|
44
|
+
carry = carry / 58 >>> 0;
|
|
45
|
+
}
|
|
46
|
+
length = j;
|
|
47
|
+
}
|
|
48
|
+
// Skip leading zeros in base58 result
|
|
49
|
+
let it = size - length;
|
|
50
|
+
while(it < size && b58[it] === 0)it++;
|
|
51
|
+
// Translate the result into a string.
|
|
52
|
+
let str = '';
|
|
53
|
+
for(let i = 0; i < zeros; i++)str += '1';
|
|
54
|
+
for(; it < size; it++)str += ALPHABET[b58[it]];
|
|
55
|
+
return str;
|
|
23
56
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
57
|
+
function decodeBase58(str) {
|
|
58
|
+
if (str.length === 0) return new Uint8Array();
|
|
59
|
+
// Count leading zeros
|
|
60
|
+
let zeros = 0;
|
|
61
|
+
while(zeros < str.length && str[zeros] === '1')zeros++;
|
|
62
|
+
// Allocate enough space in big-endian base256 representation.
|
|
63
|
+
// log(58) / log(256), rounded up.
|
|
64
|
+
const size = (str.length - zeros) * 733 / 1000 + 1 >>> 0;
|
|
65
|
+
const b256 = new Uint8Array(size);
|
|
66
|
+
let length = 0;
|
|
67
|
+
for(let i = zeros; i < str.length; i++){
|
|
68
|
+
const value = ALPHABET_MAP[str[i]];
|
|
69
|
+
if (value === undefined) throw new Error('Invalid base58 character');
|
|
70
|
+
let carry = value;
|
|
71
|
+
let j = 0;
|
|
72
|
+
for(let k = size - 1; (carry !== 0 || j < length) && k >= 0; k--, j++){
|
|
73
|
+
carry += 58 * b256[k] >>> 0;
|
|
74
|
+
b256[k] = (carry & 0xff) >>> 0;
|
|
75
|
+
carry = carry >> 8 >>> 0;
|
|
76
|
+
}
|
|
77
|
+
length = j;
|
|
32
78
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
transaction.feePayer = fromPubkey;
|
|
42
|
-
const serializedTransaction = transaction.serializeMessage();
|
|
43
|
-
return {
|
|
44
|
-
transaction,
|
|
45
|
-
serializedTransaction
|
|
46
|
-
};
|
|
79
|
+
// Skip leading zeros in b256
|
|
80
|
+
let it = size - length;
|
|
81
|
+
while(it < size && b256[it] === 0)it++;
|
|
82
|
+
const out = new Uint8Array(zeros + (size - it));
|
|
83
|
+
out.fill(0, 0, zeros);
|
|
84
|
+
let j = zeros;
|
|
85
|
+
while(it < size)out[j++] = b256[it++];
|
|
86
|
+
return out;
|
|
47
87
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
88
|
+
|
|
89
|
+
const logger = new logger$1.Logger('DynamicWaasWalletClient');
|
|
90
|
+
const logError = ({ message, error, context })=>{
|
|
91
|
+
if (error instanceof axios.AxiosError) {
|
|
92
|
+
core.handleAxiosError(error, message, context, logger);
|
|
93
|
+
}
|
|
94
|
+
logger.error('[DynamicWaasWalletClient] Error in node-svm client', {
|
|
95
|
+
error: error instanceof Error ? error.message : String(error),
|
|
96
|
+
context
|
|
97
|
+
});
|
|
51
98
|
};
|
|
52
|
-
async function sendTransaction({ signedTransaction, rpcUrl = 'https://api.devnet.solana.com' }) {
|
|
53
|
-
const connection = new web3_js.Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
54
|
-
const txid = await connection.sendRawTransaction(Buffer.from(signedTransaction));
|
|
55
|
-
return txid;
|
|
56
|
-
}
|
|
57
99
|
|
|
100
|
+
// Helper: normalize bytes to hex without triggering Buffer.from(string, encoding) overload
|
|
101
|
+
const toHex = (bytes)=>(Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes)).toString('hex');
|
|
58
102
|
class DynamicSvmWalletClient extends node.DynamicWalletClient {
|
|
59
103
|
/**
|
|
60
104
|
* Creates a wallet account on the Solana chain
|
|
61
105
|
*
|
|
62
106
|
* @param thresholdSignatureScheme The threshold signature scheme to use
|
|
63
107
|
* @returns The account address, public key hex, raw public key, and client key shares
|
|
64
|
-
*/ async createWalletAccount({ thresholdSignatureScheme, password = undefined, onError }) {
|
|
108
|
+
*/ async createWalletAccount({ thresholdSignatureScheme, password = undefined, onError, backUpToClientShareService = false }) {
|
|
65
109
|
try {
|
|
110
|
+
let ceremonyCeremonyCompleteResolver;
|
|
111
|
+
const ceremonyCompletePromise = new Promise((resolve)=>{
|
|
112
|
+
ceremonyCeremonyCompleteResolver = resolve;
|
|
113
|
+
});
|
|
66
114
|
const { rawPublicKey, externalServerKeyShares } = await this.keyGen({
|
|
67
115
|
chainName: this.chainName,
|
|
68
116
|
thresholdSignatureScheme,
|
|
69
117
|
onError,
|
|
70
118
|
onCeremonyComplete: (accountAddress, walletId)=>{
|
|
71
119
|
// update wallet map
|
|
120
|
+
const chainConfig = node.getMPCChainConfig(this.chainName);
|
|
72
121
|
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress] || {}, {
|
|
73
122
|
accountAddress,
|
|
74
123
|
walletId,
|
|
75
124
|
chainName: this.chainName,
|
|
76
125
|
thresholdSignatureScheme,
|
|
126
|
+
derivationPath: JSON.stringify(Object.fromEntries(chainConfig.derivationPath.map((value, index)=>[
|
|
127
|
+
index,
|
|
128
|
+
value
|
|
129
|
+
]))),
|
|
77
130
|
externalServerKeySharesBackupInfo: node.getExternalServerKeyShareBackupInfo()
|
|
78
131
|
});
|
|
132
|
+
this.logger.debug('walletMap updated for wallet', {
|
|
133
|
+
context: {
|
|
134
|
+
accountAddress,
|
|
135
|
+
walletId,
|
|
136
|
+
walletMap: this.walletMap
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
ceremonyCeremonyCompleteResolver(undefined);
|
|
79
140
|
}
|
|
80
141
|
});
|
|
81
|
-
|
|
82
|
-
|
|
142
|
+
// Wait for the ceremony to complete before proceeding
|
|
143
|
+
await ceremonyCompletePromise;
|
|
144
|
+
if (!rawPublicKey || !(rawPublicKey instanceof Uint8Array || typeof rawPublicKey === 'string')) {
|
|
145
|
+
throw new Error('Raw public key is not a Uint8Array or string' + typeof rawPublicKey);
|
|
83
146
|
}
|
|
84
147
|
if (!externalServerKeyShares) {
|
|
85
148
|
throw new Error('Error creating wallet account');
|
|
86
149
|
}
|
|
87
150
|
const { accountAddress } = await this.deriveAccountAddress(rawPublicKey);
|
|
88
|
-
|
|
151
|
+
await this.storeEncryptedBackupByWalletWithRetry({
|
|
89
152
|
accountAddress,
|
|
90
153
|
externalServerKeyShares,
|
|
91
|
-
password
|
|
154
|
+
password,
|
|
155
|
+
backUpToClientShareService
|
|
92
156
|
});
|
|
93
157
|
return {
|
|
94
158
|
accountAddress,
|
|
95
|
-
rawPublicKey
|
|
159
|
+
rawPublicKey,
|
|
96
160
|
externalServerKeyShares
|
|
97
161
|
};
|
|
98
162
|
} catch (error) {
|
|
99
|
-
|
|
163
|
+
logError({
|
|
164
|
+
message: ERROR_CREATE_WALLET_ACCOUNT,
|
|
165
|
+
error: error,
|
|
166
|
+
context: {}
|
|
167
|
+
});
|
|
100
168
|
throw new Error(ERROR_CREATE_WALLET_ACCOUNT);
|
|
101
169
|
}
|
|
102
170
|
}
|
|
103
171
|
// Function to properly derive account address
|
|
104
172
|
async deriveAccountAddress(rawPublicKey) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
173
|
+
function ensure32(u8) {
|
|
174
|
+
if (u8.length !== 32) throw new Error(`Invalid pubkey length: ${u8.length}`);
|
|
175
|
+
return u8;
|
|
176
|
+
}
|
|
177
|
+
const pubKeyBytes = typeof rawPublicKey === 'string' ? new Uint8Array(Buffer.from(rawPublicKey, 'hex')) : rawPublicKey;
|
|
178
|
+
const accountAddress = new web3_js.PublicKey(ensure32(pubKeyBytes)).toBase58();
|
|
108
179
|
return {
|
|
109
180
|
accountAddress
|
|
110
181
|
};
|
|
@@ -115,67 +186,90 @@ class DynamicSvmWalletClient extends node.DynamicWalletClient {
|
|
|
115
186
|
* @param message The message to sign (Uint8Array)
|
|
116
187
|
* @param accountAddress Solana address (base58 encoded)
|
|
117
188
|
* @param password The password for encrypted backup shares
|
|
118
|
-
*/ async signMessage({ message, accountAddress, password = undefined }) {
|
|
119
|
-
|
|
120
|
-
accountAddress,
|
|
121
|
-
password,
|
|
122
|
-
walletOperation: node.WalletOperation.SIGN_MESSAGE
|
|
123
|
-
});
|
|
189
|
+
*/ async signMessage({ message, accountAddress, password = undefined, externalServerKeyShares }) {
|
|
190
|
+
// Validate inputs early
|
|
124
191
|
if (!accountAddress) {
|
|
125
192
|
throw new Error('Account address is required');
|
|
126
193
|
}
|
|
127
194
|
try {
|
|
195
|
+
// Attempt to recover key shares from backup if not provided
|
|
196
|
+
await this.ensureKeySharesRecovered({
|
|
197
|
+
accountAddress,
|
|
198
|
+
password,
|
|
199
|
+
walletOperation: node.WalletOperation.SIGN_MESSAGE,
|
|
200
|
+
externalServerKeyShares,
|
|
201
|
+
errorMessage: 'External server key shares are required to sign a message. No backup shares available for recovery.'
|
|
202
|
+
});
|
|
203
|
+
const messageBytes = typeof message === 'string' ? new TextEncoder().encode(message) : message;
|
|
204
|
+
const messageHex = toHex(messageBytes);
|
|
128
205
|
const signatureEd25519 = await this.sign({
|
|
129
|
-
message,
|
|
130
|
-
accountAddress
|
|
206
|
+
message: messageHex,
|
|
207
|
+
accountAddress,
|
|
131
208
|
chainName: this.chainName,
|
|
132
|
-
password
|
|
209
|
+
password,
|
|
210
|
+
externalServerKeyShares
|
|
133
211
|
});
|
|
134
|
-
|
|
135
|
-
return base58Signature;
|
|
212
|
+
return encodeBase58(signatureEd25519);
|
|
136
213
|
} catch (error) {
|
|
137
|
-
|
|
214
|
+
logError({
|
|
215
|
+
message: 'Error signing message:',
|
|
216
|
+
error: error,
|
|
217
|
+
context: {
|
|
218
|
+
accountAddress
|
|
219
|
+
}
|
|
220
|
+
});
|
|
138
221
|
throw error;
|
|
139
222
|
}
|
|
140
223
|
}
|
|
141
|
-
|
|
224
|
+
//todo:should txn just be a string?
|
|
225
|
+
async signTransaction({ senderAddress, transaction, password = undefined, externalServerKeyShares }) {
|
|
226
|
+
// Validate inputs early
|
|
227
|
+
if (!senderAddress) {
|
|
228
|
+
throw new Error('Sender address is required');
|
|
229
|
+
}
|
|
142
230
|
await this.verifyPassword({
|
|
143
231
|
accountAddress: senderAddress,
|
|
144
232
|
password,
|
|
145
233
|
walletOperation: node.WalletOperation.SIGN_TRANSACTION
|
|
146
234
|
});
|
|
147
235
|
try {
|
|
236
|
+
// Attempt to recover key shares from backup if not provided
|
|
237
|
+
await this.ensureKeySharesRecovered({
|
|
238
|
+
accountAddress: senderAddress,
|
|
239
|
+
password,
|
|
240
|
+
walletOperation: node.WalletOperation.SIGN_TRANSACTION,
|
|
241
|
+
externalServerKeyShares,
|
|
242
|
+
errorMessage: 'External server key shares are required to sign transaction. No backup shares available for recovery.'
|
|
243
|
+
});
|
|
148
244
|
let messageToSign;
|
|
149
|
-
if (transaction
|
|
150
|
-
|
|
245
|
+
if (typeof transaction === 'string') {
|
|
246
|
+
messageToSign = transaction.startsWith('0x') ? transaction.slice(2) : transaction;
|
|
247
|
+
} else if (transaction instanceof web3_js.VersionedTransaction) {
|
|
151
248
|
const messageBytes = transaction.message.serialize();
|
|
152
|
-
messageToSign =
|
|
249
|
+
messageToSign = toHex(messageBytes);
|
|
153
250
|
} else {
|
|
154
|
-
// For legacy transactions, serialize the message
|
|
155
251
|
const messageBytes = transaction.serializeMessage();
|
|
156
|
-
messageToSign = Buffer.
|
|
252
|
+
messageToSign = toHex(Buffer.isBuffer(messageBytes) ? messageBytes : messageBytes);
|
|
157
253
|
}
|
|
158
254
|
const signatureEd25519 = await this.sign({
|
|
159
255
|
message: messageToSign,
|
|
160
256
|
accountAddress: senderAddress,
|
|
161
257
|
chainName: this.chainName,
|
|
162
|
-
password
|
|
258
|
+
password,
|
|
259
|
+
externalServerKeyShares
|
|
163
260
|
});
|
|
164
261
|
if (!signatureEd25519) {
|
|
165
262
|
throw new Error('Signature is undefined');
|
|
166
263
|
}
|
|
167
|
-
|
|
168
|
-
const signedTransaction = addSignatureToTransaction({
|
|
169
|
-
transaction,
|
|
170
|
-
signature: signatureEd25519,
|
|
171
|
-
signerPublicKey: senderPublicKey
|
|
172
|
-
});
|
|
173
|
-
return signedTransaction;
|
|
264
|
+
return encodeBase58(signatureEd25519);
|
|
174
265
|
} catch (error) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
266
|
+
logError({
|
|
267
|
+
message: 'Error in signTransaction:',
|
|
268
|
+
error: error,
|
|
269
|
+
context: {
|
|
270
|
+
senderAddress
|
|
271
|
+
}
|
|
272
|
+
});
|
|
179
273
|
throw error;
|
|
180
274
|
}
|
|
181
275
|
}
|
|
@@ -185,19 +279,17 @@ class DynamicSvmWalletClient extends node.DynamicWalletClient {
|
|
|
185
279
|
* @param accountAddress The account address to export the private key for
|
|
186
280
|
* @param password The password for encrypted backup shares
|
|
187
281
|
* @returns The private key
|
|
188
|
-
*/ async exportPrivateKey({ accountAddress, password = undefined }) {
|
|
282
|
+
*/ async exportPrivateKey({ accountAddress, password = undefined, externalServerKeyShares }) {
|
|
189
283
|
const { derivedPrivateKey } = await this.exportKey({
|
|
190
284
|
accountAddress,
|
|
191
285
|
chainName: this.chainName,
|
|
192
|
-
password
|
|
286
|
+
password,
|
|
287
|
+
externalServerKeyShares
|
|
193
288
|
});
|
|
194
289
|
if (!derivedPrivateKey) {
|
|
195
290
|
throw new Error('Derived private key is undefined');
|
|
196
291
|
}
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
derivedPrivateKey: encodedPrivateKey
|
|
200
|
-
};
|
|
292
|
+
return derivedPrivateKey;
|
|
201
293
|
}
|
|
202
294
|
/**
|
|
203
295
|
* Exports the private key for a given account address
|
|
@@ -220,20 +312,19 @@ class DynamicSvmWalletClient extends node.DynamicWalletClient {
|
|
|
220
312
|
* @param privateKey The private key to convert
|
|
221
313
|
* @returns The hex string
|
|
222
314
|
*/ decodePrivateKeyForSolana(privateKey) {
|
|
223
|
-
const decoded =
|
|
224
|
-
|
|
225
|
-
return Buffer.from(
|
|
315
|
+
const decoded = decodeBase58(privateKey); // 64 bytes
|
|
316
|
+
if (decoded.length !== 64) throw new Error('Invalid Solana secret key length');
|
|
317
|
+
return Buffer.from(decoded.slice(0, 32)).toString('hex');
|
|
226
318
|
}
|
|
227
319
|
getPublicKeyFromPrivateKey(privateKey) {
|
|
228
|
-
const
|
|
229
|
-
const keypair = web3_js.Keypair.fromSecretKey(
|
|
230
|
-
|
|
231
|
-
return publicKeyBase58;
|
|
320
|
+
const secret = decodeBase58(privateKey);
|
|
321
|
+
const keypair = web3_js.Keypair.fromSecretKey(secret);
|
|
322
|
+
return keypair.publicKey.toBase58();
|
|
232
323
|
}
|
|
233
324
|
encodePublicKey(publicKey) {
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
return
|
|
325
|
+
// Ensure a plain Uint8Array is passed to PublicKey
|
|
326
|
+
const bytes = publicKey instanceof Uint8Array ? publicKey : new Uint8Array(publicKey);
|
|
327
|
+
return new web3_js.PublicKey(bytes).toBase58();
|
|
237
328
|
}
|
|
238
329
|
/**
|
|
239
330
|
* Imports the private key for a given account address
|
|
@@ -243,7 +334,11 @@ class DynamicSvmWalletClient extends node.DynamicWalletClient {
|
|
|
243
334
|
* @param thresholdSignatureScheme The threshold signature scheme to use
|
|
244
335
|
* @param password The password for encrypted backup shares
|
|
245
336
|
* @returns The account address, raw public key, and client key shares
|
|
246
|
-
*/ async importPrivateKey({ privateKey, chainName, thresholdSignatureScheme, password = undefined, onError }) {
|
|
337
|
+
*/ async importPrivateKey({ privateKey, chainName, thresholdSignatureScheme, password = undefined, onError, backUpToClientShareService = false }) {
|
|
338
|
+
let ceremonyCeremonyCompleteResolver;
|
|
339
|
+
const ceremonyCompletePromise = new Promise((resolve)=>{
|
|
340
|
+
ceremonyCeremonyCompleteResolver = resolve;
|
|
341
|
+
});
|
|
247
342
|
//get public key from private key
|
|
248
343
|
const publicKey = this.getPublicKeyFromPrivateKey(privateKey);
|
|
249
344
|
const formattedPrivateKey = await this.decodePrivateKeyForSolana(privateKey);
|
|
@@ -254,15 +349,23 @@ class DynamicSvmWalletClient extends node.DynamicWalletClient {
|
|
|
254
349
|
onError,
|
|
255
350
|
onCeremonyComplete: (accountAddress, walletId)=>{
|
|
256
351
|
// update wallet map
|
|
352
|
+
const chainConfig = node.getMPCChainConfig(this.chainName);
|
|
257
353
|
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress] || {}, {
|
|
258
354
|
accountAddress,
|
|
259
355
|
walletId,
|
|
260
356
|
chainName: this.chainName,
|
|
261
357
|
thresholdSignatureScheme,
|
|
358
|
+
derivationPath: JSON.stringify(Object.fromEntries(chainConfig.derivationPath.map((value, index)=>[
|
|
359
|
+
index,
|
|
360
|
+
value
|
|
361
|
+
]))),
|
|
262
362
|
externalServerKeySharesBackupInfo: node.getExternalServerKeyShareBackupInfo()
|
|
263
363
|
});
|
|
364
|
+
ceremonyCeremonyCompleteResolver(undefined);
|
|
264
365
|
}
|
|
265
366
|
});
|
|
367
|
+
// Wait for the ceremony to complete before proceeding
|
|
368
|
+
await ceremonyCompletePromise;
|
|
266
369
|
if (!rawPublicKey || !externalServerKeyShares) {
|
|
267
370
|
throw new Error('Error creating wallet account');
|
|
268
371
|
}
|
|
@@ -270,11 +373,11 @@ class DynamicSvmWalletClient extends node.DynamicWalletClient {
|
|
|
270
373
|
if (accountAddress !== publicKey) {
|
|
271
374
|
throw new Error(`Public key mismatch: derived address ${accountAddress} !== public key ${publicKey}`);
|
|
272
375
|
}
|
|
273
|
-
|
|
274
|
-
void this.storeEncryptedBackupByWalletWithRetry({
|
|
376
|
+
await this.storeEncryptedBackupByWalletWithRetry({
|
|
275
377
|
accountAddress,
|
|
276
378
|
externalServerKeyShares,
|
|
277
|
-
password
|
|
379
|
+
password,
|
|
380
|
+
backUpToClientShareService
|
|
278
381
|
});
|
|
279
382
|
return {
|
|
280
383
|
accountAddress,
|
|
@@ -287,17 +390,222 @@ class DynamicSvmWalletClient extends node.DynamicWalletClient {
|
|
|
287
390
|
const svmWallets = wallets.filter((wallet)=>wallet.chainName === 'solana');
|
|
288
391
|
return svmWallets;
|
|
289
392
|
}
|
|
290
|
-
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl }){
|
|
393
|
+
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, enableMPCAccelerator }){
|
|
291
394
|
super({
|
|
292
395
|
environmentId,
|
|
293
396
|
baseApiUrl,
|
|
294
|
-
baseMPCRelayApiUrl
|
|
295
|
-
|
|
397
|
+
baseMPCRelayApiUrl,
|
|
398
|
+
enableMPCAccelerator
|
|
399
|
+
}), this.chainName = 'SVM';
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function getBalance({ address, rpcUrl = node.SOLANA_RPC_URL }) {
|
|
404
|
+
const connection = new web3_js.Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
405
|
+
const balance = await connection.getBalance(new web3_js.PublicKey(address));
|
|
406
|
+
return balance;
|
|
407
|
+
}
|
|
408
|
+
async function createSolanaTransaction({ senderSolanaAddress, amount, to, rpcUrl = 'https://api.devnet.solana.com' }) {
|
|
409
|
+
const connection = new web3_js.Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
410
|
+
const balance = await getBalance({
|
|
411
|
+
address: senderSolanaAddress,
|
|
412
|
+
rpcUrl
|
|
413
|
+
});
|
|
414
|
+
if (balance < amount * 1e9) {
|
|
415
|
+
throw new Error('Insufficient balance');
|
|
296
416
|
}
|
|
417
|
+
const fromPubkey = new web3_js.PublicKey(senderSolanaAddress);
|
|
418
|
+
const transaction = new web3_js.Transaction().add(web3_js.SystemProgram.transfer({
|
|
419
|
+
fromPubkey: fromPubkey,
|
|
420
|
+
toPubkey: new web3_js.PublicKey(to),
|
|
421
|
+
lamports: amount * 1e9
|
|
422
|
+
}));
|
|
423
|
+
const { blockhash } = await connection.getLatestBlockhash();
|
|
424
|
+
transaction.recentBlockhash = blockhash;
|
|
425
|
+
transaction.feePayer = fromPubkey;
|
|
426
|
+
const serializedTransaction = transaction.serializeMessage();
|
|
427
|
+
return {
|
|
428
|
+
transaction,
|
|
429
|
+
serializedTransaction
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const addSignatureToTransaction = ({ transaction, signature, signerPublicKey })=>{
|
|
433
|
+
transaction.addSignature(signerPublicKey, Buffer.from(signature));
|
|
434
|
+
return transaction;
|
|
435
|
+
};
|
|
436
|
+
function attachSignature({ transaction, signatureBase58, senderAddress }) {
|
|
437
|
+
const sigBytes = decodeBase58(signatureBase58);
|
|
438
|
+
const signerPubkey = new web3_js.PublicKey(senderAddress);
|
|
439
|
+
return addSignatureToTransaction({
|
|
440
|
+
transaction,
|
|
441
|
+
signature: sigBytes,
|
|
442
|
+
signerPublicKey: signerPubkey
|
|
443
|
+
});
|
|
297
444
|
}
|
|
445
|
+
async function sendTransaction({ signedTransaction, rpcUrl = 'https://api.devnet.solana.com' }) {
|
|
446
|
+
const connection = new web3_js.Connection(rpcUrl != null ? rpcUrl : 'https://api.devnet.solana.com');
|
|
447
|
+
const txid = await connection.sendRawTransaction(Buffer.from(signedTransaction));
|
|
448
|
+
return txid;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Creates a delegated SVM wallet client for functional operations
|
|
453
|
+
*/ const createDelegatedSvmWalletClient = ({ environmentId, baseApiUrl, baseMPCRelayApiUrl, apiKey, debug = false })=>{
|
|
454
|
+
const baseClient = node.createDelegatedWalletClient({
|
|
455
|
+
environmentId,
|
|
456
|
+
baseApiUrl,
|
|
457
|
+
baseMPCRelayApiUrl,
|
|
458
|
+
apiKey,
|
|
459
|
+
debug
|
|
460
|
+
});
|
|
461
|
+
const svmClient = _extends({}, baseClient, {
|
|
462
|
+
chainName: 'SVM'
|
|
463
|
+
});
|
|
464
|
+
return svmClient;
|
|
465
|
+
};
|
|
466
|
+
/**
|
|
467
|
+
* Signs a message using delegated signing for SVM
|
|
468
|
+
*/ const delegatedSignMessage = async (client, { walletId, walletApiKey, keyShare, message, isFormatted = false })=>{
|
|
469
|
+
try {
|
|
470
|
+
// Use the delegated sign message function from node package
|
|
471
|
+
const signatureEd25519 = await node.delegatedSignMessage(client, {
|
|
472
|
+
walletId,
|
|
473
|
+
walletApiKey,
|
|
474
|
+
keyShare,
|
|
475
|
+
message,
|
|
476
|
+
chainName: client.chainName,
|
|
477
|
+
isFormatted
|
|
478
|
+
});
|
|
479
|
+
// Use PublicKey to encode signature as base58 (SVM format)
|
|
480
|
+
const base58Signature = encodeBase58(signatureEd25519);
|
|
481
|
+
return base58Signature;
|
|
482
|
+
} catch (error) {
|
|
483
|
+
logError({
|
|
484
|
+
message: 'Error in delegatedSignMessage',
|
|
485
|
+
error: error,
|
|
486
|
+
context: {
|
|
487
|
+
walletId
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
throw error;
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
/**
|
|
494
|
+
* Signs a transaction using delegated signing for SVM
|
|
495
|
+
*
|
|
496
|
+
* @param client - The delegated SVM wallet client
|
|
497
|
+
* @param options - Signing options
|
|
498
|
+
* @param options.walletId - The wallet ID
|
|
499
|
+
* @param options.walletApiKey - The wallet API key
|
|
500
|
+
* @param options.keyShare - The server key share
|
|
501
|
+
* @param options.transaction - The transaction to sign (VersionedTransaction or Transaction)
|
|
502
|
+
* @param options.signerAddress - Optional. The address that should sign the transaction.
|
|
503
|
+
* If not provided, defaults to the first signer (VersionedTransaction)
|
|
504
|
+
* or fee payer (Transaction). Use this for gasless transactions where
|
|
505
|
+
* a separate fee payer pays the fees.
|
|
506
|
+
*
|
|
507
|
+
* @returns The partially signed transaction with the signature attached for the specified signer
|
|
508
|
+
*
|
|
509
|
+
* @example
|
|
510
|
+
* // Standard transaction where sender is also fee payer
|
|
511
|
+
* const signedTx = await delegatedSignTransaction(client, {
|
|
512
|
+
* walletId,
|
|
513
|
+
* walletApiKey,
|
|
514
|
+
* keyShare,
|
|
515
|
+
* transaction,
|
|
516
|
+
* });
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
* // Gasless transaction with separate fee payer
|
|
520
|
+
* const transaction = new Transaction();
|
|
521
|
+
* transaction.feePayer = feePayerPublicKey; // Set the actual fee payer
|
|
522
|
+
* transaction.add(instruction); // Instruction that requires sender signature
|
|
523
|
+
*
|
|
524
|
+
* const signedTx = await delegatedSignTransaction(client, {
|
|
525
|
+
* walletId,
|
|
526
|
+
* walletApiKey,
|
|
527
|
+
* keyShare,
|
|
528
|
+
* transaction,
|
|
529
|
+
* signerAddress: senderAddress, // Explicitly specify who signs
|
|
530
|
+
* });
|
|
531
|
+
*
|
|
532
|
+
*/ const delegatedSignTransaction = async (client, { walletId, walletApiKey, keyShare, transaction, signerAddress })=>{
|
|
533
|
+
try {
|
|
534
|
+
let messageToSign;
|
|
535
|
+
if (transaction instanceof web3_js.VersionedTransaction) {
|
|
536
|
+
// For versioned transactions, we need to sign the message directly
|
|
537
|
+
const messageBytes = transaction.message.serialize();
|
|
538
|
+
messageToSign = Buffer.from(Array.from(messageBytes)).toString('hex');
|
|
539
|
+
} else {
|
|
540
|
+
// For legacy transactions, serialize the message
|
|
541
|
+
const messageBytes = transaction.serializeMessage();
|
|
542
|
+
messageToSign = messageBytes.toString('hex');
|
|
543
|
+
}
|
|
544
|
+
// Use the delegated sign message function from node package
|
|
545
|
+
const signatureEd25519 = await node.delegatedSignMessage(client, {
|
|
546
|
+
walletId,
|
|
547
|
+
walletApiKey,
|
|
548
|
+
keyShare,
|
|
549
|
+
message: messageToSign,
|
|
550
|
+
chainName: client.chainName,
|
|
551
|
+
isFormatted: false
|
|
552
|
+
});
|
|
553
|
+
if (!signatureEd25519) {
|
|
554
|
+
throw new Error('Signature is undefined');
|
|
555
|
+
}
|
|
556
|
+
const resolvedSignerAddress = signerAddress != null ? signerAddress : (()=>{
|
|
557
|
+
var _transaction_feePayer;
|
|
558
|
+
if (transaction instanceof web3_js.VersionedTransaction) {
|
|
559
|
+
var _signers_;
|
|
560
|
+
const signers = transaction.message.staticAccountKeys;
|
|
561
|
+
var _signers__toBase58;
|
|
562
|
+
return (_signers__toBase58 = (_signers_ = signers[0]) == null ? void 0 : _signers_.toBase58()) != null ? _signers__toBase58 : '';
|
|
563
|
+
}
|
|
564
|
+
var _transaction_feePayer_toBase58;
|
|
565
|
+
return (_transaction_feePayer_toBase58 = (_transaction_feePayer = transaction.feePayer) == null ? void 0 : _transaction_feePayer.toBase58()) != null ? _transaction_feePayer_toBase58 : '';
|
|
566
|
+
})();
|
|
567
|
+
if (!resolvedSignerAddress) {
|
|
568
|
+
throw new Error('Could not determine signer address. Provide signerAddress explicitly or ensure transaction has a fee payer.');
|
|
569
|
+
}
|
|
570
|
+
let signerPublicKey;
|
|
571
|
+
try {
|
|
572
|
+
signerPublicKey = new web3_js.PublicKey(resolvedSignerAddress);
|
|
573
|
+
} catch (error) {
|
|
574
|
+
throw new Error(`Invalid signer address: ${resolvedSignerAddress}. ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
575
|
+
}
|
|
576
|
+
const signedTransaction = addSignatureToTransaction({
|
|
577
|
+
transaction,
|
|
578
|
+
signature: signatureEd25519,
|
|
579
|
+
signerPublicKey
|
|
580
|
+
});
|
|
581
|
+
return signedTransaction;
|
|
582
|
+
} catch (error) {
|
|
583
|
+
logError({
|
|
584
|
+
message: 'Error in delegatedSignTransaction',
|
|
585
|
+
error: error,
|
|
586
|
+
context: {
|
|
587
|
+
walletId
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
throw error;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
/**
|
|
594
|
+
* Revoke delegation - delegates to the node package
|
|
595
|
+
*/ const revokeDelegation = async (client, params)=>{
|
|
596
|
+
return node.revokeDelegation(client, params);
|
|
597
|
+
};
|
|
298
598
|
|
|
299
599
|
exports.DynamicSvmWalletClient = DynamicSvmWalletClient;
|
|
600
|
+
exports.ERROR_CREATE_WALLET_ACCOUNT = ERROR_CREATE_WALLET_ACCOUNT;
|
|
300
601
|
exports.addSignatureToTransaction = addSignatureToTransaction;
|
|
602
|
+
exports.attachSignature = attachSignature;
|
|
603
|
+
exports.createDelegatedSvmWalletClient = createDelegatedSvmWalletClient;
|
|
301
604
|
exports.createSolanaTransaction = createSolanaTransaction;
|
|
605
|
+
exports.decodeBase58 = decodeBase58;
|
|
606
|
+
exports.delegatedSignMessage = delegatedSignMessage;
|
|
607
|
+
exports.delegatedSignTransaction = delegatedSignTransaction;
|
|
608
|
+
exports.encodeBase58 = encodeBase58;
|
|
302
609
|
exports.getBalance = getBalance;
|
|
610
|
+
exports.revokeDelegation = revokeDelegation;
|
|
303
611
|
exports.sendTransaction = sendTransaction;
|