wative 1.0.37 → 1.0.39

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.
@@ -0,0 +1,515 @@
1
+ import * as path from "path";
2
+ import { readFileSync } from 'fs';
3
+ import { utils, Server } from 'ssh2';
4
+ import { timingSafeEqual } from 'crypto';
5
+ import { checkFileExistence } from './utils';
6
+ import { SignMessageParams, SubAccountMessageParams } from './types';
7
+ import * as log4js from "log4js";
8
+ import { ethers } from 'ethers';
9
+
10
+ const { WativeCore } = require("wative-core");
11
+ const { Connection, PublicKey, VersionedTransaction, VersionedMessage } = require("@solana/web3.js");
12
+ const bs58 = require("bs58");
13
+ const { Keypair, TransactionMessage, ComputeBudgetInstruction, TransactionInstruction, ComputeBudgetProgram } = require("@solana/web3.js");
14
+ const { Wallet } = require("@coral-xyz/anchor");
15
+ const logger = log4js.getLogger();
16
+ logger.level = "info";
17
+
18
+ export class WativeWielderServer {
19
+ private wativeCore: any;
20
+ private _allowedAppIds: Buffer[] = [];
21
+ private _allowedPubKeys: any[] = [];
22
+ private _allowedKeystoreIds: string[][] = [];
23
+
24
+ private _idRsaPrivatePath: string;
25
+ private _idRsaPassphrase: string | undefined;
26
+
27
+ private _keystoreDirectoryPath: string;
28
+ private _sessionIdAppIdMap: any = {};
29
+
30
+ public constructor(
31
+ idRsaPrivatePath: string,
32
+ idRsaPassphrase: string | undefined,
33
+
34
+ allowedAppIds: string[],
35
+ allowedPublicKeyPath: string[],
36
+ allowedKeystoreIds: string[][],
37
+
38
+ keystoreDirectoryPath: string,
39
+ keystoreIds: string[],
40
+ passwords: string[],
41
+ ) {
42
+ checkFileExistence(idRsaPrivatePath);
43
+
44
+ if (
45
+ allowedAppIds.length === 0 ||
46
+ allowedPublicKeyPath.length === 0 ||
47
+ allowedKeystoreIds.length === 0 ||
48
+ allowedAppIds.length !== allowedPublicKeyPath.length ||
49
+ allowedAppIds.length !== allowedKeystoreIds.length
50
+ ) {
51
+ throw new Error('WativeWielderServer:: constructor: Invaild AllowedApp');
52
+ }
53
+
54
+ for (let i = 0; i < allowedKeystoreIds.length; i++) {
55
+ for (let j = 0; j < allowedKeystoreIds[i].length; j++) {
56
+ const keystoreId = allowedKeystoreIds[i][j];
57
+
58
+ if (!keystoreIds.includes(keystoreId)) {
59
+ throw new Error('WativeWielderServer:: constructor: Invaild AllowedKeystoreIds');
60
+ }
61
+ }
62
+ }
63
+
64
+ for (let allowedAppId of allowedAppIds) {
65
+ this._allowedAppIds.push(
66
+ Buffer.from(allowedAppId)
67
+ )
68
+ }
69
+
70
+ for (let _allowedPublicKeyFilePath of allowedPublicKeyPath) {
71
+ let _parsedKey = utils.parseKey(readFileSync(_allowedPublicKeyFilePath));
72
+ this._allowedPubKeys.push(_parsedKey)
73
+ }
74
+
75
+ this.wativeCore = new WativeCore(keystoreDirectoryPath, keystoreIds, passwords);
76
+
77
+ this._idRsaPrivatePath = idRsaPrivatePath;
78
+ this._idRsaPassphrase = idRsaPassphrase;
79
+ this._allowedKeystoreIds = allowedKeystoreIds;
80
+
81
+ this._keystoreDirectoryPath = keystoreDirectoryPath;
82
+ }
83
+
84
+ public listen(host: string, port: number) {
85
+ new Server({
86
+ hostKeys: [{
87
+ key: readFileSync(this._idRsaPrivatePath),
88
+ passphrase: this._idRsaPassphrase
89
+ }]
90
+ }, (client: any) => {
91
+ client.on('authentication', (ctx: any) => {
92
+ let allowed = true;
93
+
94
+ if (this)
95
+ if (!this.checkValue(Buffer.from(ctx.username), 'appId')) {
96
+ allowed = false;
97
+ }
98
+
99
+ if (ctx.method === 'publickey' && allowed) {
100
+ let allowedAppIndex = this.getBytesIndex(Buffer.from(ctx.username), this._allowedAppIds);
101
+
102
+ let allowedPubKey = this._allowedPubKeys[allowedAppIndex];
103
+
104
+ if (ctx.key.algo !== allowedPubKey.type
105
+ || !this.checkValue(ctx.key.data, 'publicKey')
106
+ || (ctx.signature && allowedPubKey.verify(ctx.blob, ctx.signature, ctx.hashAlgo) !== true)) {
107
+ return ctx.reject();
108
+ }
109
+ } else {
110
+ return ctx.reject();
111
+ }
112
+
113
+ if (allowed) {
114
+ const sessionID = client._protocol._kex.sessionID.toString('hex');
115
+ this._sessionIdAppIdMap[sessionID] = Buffer.from(ctx.username);
116
+ ctx.accept();
117
+ } else {
118
+ ctx.reject();
119
+ }
120
+ }).on('ready', () => {
121
+
122
+ client.on('session', (accept: any, reject: any) => {
123
+ const session = accept();
124
+ session.once('exec', async (accept: any, reject: any, info: any) => {
125
+ const sessionID = client._protocol._kex.sessionID.toString('hex');
126
+ const result = await this.web3Request(sessionID, info.command);
127
+ const stream = accept();
128
+ if (!stream) {
129
+ return;
130
+ }
131
+
132
+ if (!JSON.stringify(result)) {
133
+ stream.write("ssh2 connect error");
134
+ stream.exit(0);
135
+ stream.end();
136
+ } else {
137
+ stream.write(JSON.stringify(result));
138
+ stream.exit(0);
139
+ stream.end();
140
+ }
141
+ })
142
+ });
143
+ }).on('close', () => {
144
+ logger.info('Client disconnected');
145
+ }).on('error', (err: any) => {
146
+ logger.error('err: ', err.message);
147
+ });
148
+ }).listen(port, host, function () {
149
+ logger.info('Listening on port ' + port);
150
+ })
151
+ }
152
+
153
+ private async web3Request(sessionID: string, message: string) {
154
+ try {
155
+ const appId = this._sessionIdAppIdMap[sessionID];
156
+ const appIndex = this.getBytesIndex(appId, this._allowedAppIds);
157
+ if (appIndex < 0) {
158
+ return { status: false, msg: 'app not found' };
159
+ }
160
+
161
+ let messageData = JSON.parse(message);
162
+ logger.info("sign message: ", message);
163
+ let _messageMethod: string = messageData.method;
164
+ let _keystoreId: string = messageData.keystoreId || messageData.params.keystoreId;
165
+ let _chainId: string = messageData.chainId || messageData.params.chainId;
166
+
167
+ const allowedKeystoreIds = this._allowedKeystoreIds[appIndex];
168
+ if (!allowedKeystoreIds.includes(_keystoreId)) {
169
+ return { status: false, msg: 'keystore not allowed' };
170
+ }
171
+
172
+ let _data: any;
173
+ if (_messageMethod === "get_root_account") {
174
+ _data = this.getRootAccount(_keystoreId, _chainId);
175
+ // _data = this.wativeCore.account.getRootAccounts(_keystoreId);
176
+
177
+ } else if (_messageMethod === "get_sub_account") {
178
+ let _message: SubAccountMessageParams = messageData.params;
179
+
180
+ _data = this.getSubAccount(_keystoreId, _message.chainId, _message?.startIndex, _message?.endIndex);
181
+ } else if (_messageMethod === "sign") {
182
+ let _message: SignMessageParams = messageData.params;
183
+ try {
184
+ _data = await this.wativeCore.account.signTransaction(_message.txParams.from, _message.txParams, _message.rpcUrl);
185
+ } catch (err: any) {
186
+ logger.error("sign error: ", err.message);
187
+ return { status: false, msg: err.msg };
188
+ }
189
+ } else if (_messageMethod === "sign_and_send") {
190
+ let _message: SignMessageParams = messageData.params;
191
+ try {
192
+ let txHash = await this.wativeCore.account.signAndSendTx(_message.txParams, _message.rpcUrl);
193
+ _data = {
194
+ transactionHash: txHash
195
+ }
196
+ } catch (err: any) {
197
+ return { status: false, msg: err.msg };
198
+ }
199
+ } else if (_messageMethod === "solana_sign") {
200
+ try {
201
+ let _message = messageData.params;
202
+ _data = await this.signSolanaTransaction(_message.txParams.from, _message.txParams.data, _message.txParams.lookup_tables, _message.rpcUrl, _message.priority_fee);
203
+ } catch (err: any) {
204
+ logger.error("sign error: ", err.message);
205
+ return { status: false, msg: err.msg };
206
+ }
207
+ } else if (_messageMethod === "solana_send") {
208
+ try {
209
+ let _message = messageData.params;
210
+ _data = await this.sendSignedSolanaTransaction(_message.txParams.from, _message.txParams.signature, _message.txParams.data, _message.rpcUrl);
211
+ } catch (err: any) {
212
+ logger.error("sign error: ", err.message);
213
+ return { status: false, msg: err.msg };
214
+ }
215
+
216
+ } else if (_messageMethod === "sign_message") {
217
+ try {
218
+ let _message = messageData.params;
219
+ logger.debug("sign message: ", _message);
220
+ _data = await this.signMessage(_message.account, _message.message);
221
+ } catch (err: any) {
222
+ logger.error("sign error: ", err.message);
223
+ return { status: false, msg: err.msg };
224
+ }
225
+ } else if (_messageMethod === "sign_typed_data") {
226
+ try {
227
+ let _message = messageData.params;
228
+ logger.debug("sign message: ", _message);
229
+ _data = await this.signTypedData(_message.account, _message.domain, _message.types_name, _message.types, _message.message)
230
+ } catch (err: any) {
231
+ logger.error("sign error: ", err.message);
232
+ return { status: false, msg: err.msg };
233
+ }
234
+ } else {
235
+ return { status: false, msg: 'message method not find' };
236
+ }
237
+
238
+ return { status: true, data: _data };
239
+ } catch (err: any) {
240
+ logger.error("sign error: ", err.message);
241
+ return { status: false, msg: err.msg };
242
+ }
243
+ }
244
+
245
+ private checkValue(input: Buffer, checkTypes: string) {
246
+
247
+ if (checkTypes !== 'appId' && checkTypes !== 'publicKey') {
248
+ return false;
249
+ }
250
+
251
+ let allowedSize = this._allowedAppIds.length;
252
+ for (let i = 0; i < allowedSize; i++) {
253
+ let allowed: Buffer;
254
+ if (checkTypes === 'appId') {
255
+ allowed = this._allowedAppIds[i];
256
+ } else {
257
+ allowed = this._allowedPubKeys[i].getPublicSSH();
258
+ }
259
+
260
+ const autoReject = (input.length !== allowed.length);
261
+ if (autoReject) {
262
+ allowed = input;
263
+ }
264
+ const isMatch = timingSafeEqual(input, allowed);
265
+
266
+ if (!autoReject && isMatch) {
267
+ return true;
268
+ }
269
+ }
270
+ }
271
+
272
+ private getBytesIndex(buf: Buffer, bufList: Buffer[]) {
273
+ let bufSize = bufList.length;
274
+
275
+ for (let i = 0; i < bufSize; i++) {
276
+ if (buf.length === bufList[i].length && timingSafeEqual(buf, bufList[i])) {
277
+ return i;
278
+ }
279
+ }
280
+
281
+ return -1;
282
+ }
283
+
284
+ private getRootAccount(keystoreId: string, chainId: string) {
285
+ const keystore_path = path.resolve(this._keystoreDirectoryPath, `accounts/${keystoreId}.json`);
286
+ let keystore_info: any = readFileSync(keystore_path);
287
+ keystore_info = JSON.parse(keystore_info.toString());
288
+
289
+ let chainType = this.getChainType(chainId);
290
+
291
+ let root_address;
292
+ if (chainType === "SOLANA") {
293
+ root_address = keystore_info.data[0].ciphertexts.solana.address;
294
+ } else {
295
+ root_address = keystore_info.data[0].ciphertexts.evm.address;
296
+ }
297
+ return [{
298
+ id: 0,
299
+ address: root_address,
300
+ children: keystore_info.data.length,
301
+ type: "M"
302
+ }];
303
+ }
304
+
305
+
306
+ private getChainType(chainId: string) {
307
+ if (chainId === "901" || chainId === "902" || chainId === "903") {
308
+ return "SOLANA";
309
+ }
310
+ return "EVM";
311
+ }
312
+
313
+ private getSubAccount(keystoreId: string, chainId: string, startIndex?: number, endIndex?: number) {
314
+ const keystore_path = path.resolve(this._keystoreDirectoryPath, `accounts/${keystoreId}.json`);
315
+ let keystore_info: any = readFileSync(keystore_path);
316
+ keystore_info = JSON.parse(keystore_info.toString());
317
+
318
+ let keystore_info_len = keystore_info.data.length;
319
+ let _endIndex = keystore_info_len - 1;
320
+ if (endIndex && endIndex < _endIndex) {
321
+ _endIndex = endIndex;
322
+ }
323
+
324
+ let _startIndex = 0;
325
+ if (startIndex && startIndex > 0) {
326
+ _startIndex = startIndex;
327
+ }
328
+
329
+ if (_startIndex > _endIndex) {
330
+ return [];
331
+ }
332
+
333
+ let chainType = this.getChainType(chainId);
334
+ let result: any = [];
335
+ for (let i = _startIndex; i <= _endIndex; i++) {
336
+ result.push(keystore_info.data[i].ciphertexts[chainType.toLocaleLowerCase()].address);
337
+ }
338
+
339
+ return result;
340
+ }
341
+
342
+ private async signMessage(account: string, message: string) {
343
+ let signature = await this.wativeCore.account.signMessage(account, message);
344
+ console.log(signature);
345
+ return signature;
346
+ }
347
+
348
+ private async signTypedData(account: string, message_domain: string, message_types_name: string, message_types: string, message_data: string) {
349
+ console.log("111");
350
+ let domain = JSON.parse(message_domain);
351
+ let types = JSON.parse(message_types);
352
+ let data = JSON.parse(message_data);
353
+
354
+ console.log(domain);
355
+ console.log(types);
356
+ console.log(data);
357
+ let pkl = this.wativeCore.account.showPrivateKey(account);
358
+ let wallet: any = new ethers.Wallet(pkl);
359
+ let signature = await wallet.signTypedData(domain, {
360
+ [message_types_name]: types
361
+ }, data);
362
+
363
+ console.log(signature);
364
+ return {
365
+ "status": true,
366
+ "output": signature
367
+ };
368
+ }
369
+
370
+ private async signSolanaTransaction(from: string, data: string, lookup_table_addresses: any, rpc_url: string, prioritization_fee: any) {
371
+ let connection = new Connection(rpc_url);
372
+
373
+ let lookup_table_list: any = [];
374
+ if (lookup_table_addresses) {
375
+ for (let lookup_table_address of lookup_table_addresses) {
376
+ let lookup_table = (await connection.getAddressLookupTable(new PublicKey(lookup_table_address))).value;
377
+ lookup_table_list.push(lookup_table);
378
+ await sleep(1000);
379
+ }
380
+ }
381
+
382
+ let versionedMessage = VersionedMessage.deserialize(Uint8Array.from(Buffer.from(data, "hex")));
383
+ let decompiled_transaction_message = TransactionMessage.decompile(versionedMessage);
384
+ decompiled_transaction_message.instructions.push(
385
+ ComputeBudgetProgram.setComputeUnitLimit({
386
+ units: 2000000
387
+ })
388
+ );
389
+ decompiled_transaction_message.instructions.push(
390
+ ComputeBudgetProgram.setComputeUnitPrice({
391
+ microLamports: 1000
392
+ })
393
+ );
394
+
395
+ versionedMessage = decompiled_transaction_message.compileToV0Message(lookup_table_list);
396
+ versionedMessage.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
397
+
398
+ await sleep(3000);
399
+ let versionedTransaction = new VersionedTransaction(versionedMessage);
400
+ let pkl = this.wativeCore.account.showPrivateKey(from);
401
+ const owner = Keypair.fromSecretKey(new Uint8Array(bs58.decode(pkl)));
402
+ const wallet = new Wallet(owner);
403
+
404
+ let simulate_transaction_result = await connection.simulateTransaction(versionedTransaction);
405
+ if (!simulate_transaction_result || !simulate_transaction_result.value || simulate_transaction_result.value.unitsConsumed === 0 || simulate_transaction_result.value.err) {
406
+ return {
407
+ status: false,
408
+ output: simulate_transaction_result.value.err
409
+ }
410
+ }
411
+
412
+ let message_instruction_length = decompiled_transaction_message.instructions.length;
413
+ let units_consumed = Math.trunc(simulate_transaction_result.value.unitsConsumed * 1.2);
414
+ decompiled_transaction_message.instructions[message_instruction_length - 2] = ComputeBudgetProgram.setComputeUnitLimit({
415
+ units: units_consumed
416
+ });
417
+
418
+ if (!prioritization_fee || prioritization_fee == "null") {
419
+ try {
420
+ prioritization_fee = await this.getPrioritizationFee(rpc_url);
421
+ } catch (err) {
422
+ prioritization_fee = 200000;
423
+ }
424
+ }
425
+
426
+ if (prioritization_fee < 10000) {
427
+ prioritization_fee = 10000;
428
+ }
429
+
430
+ if (prioritization_fee > 500000) {
431
+ prioritization_fee = 500000;
432
+ }
433
+
434
+ decompiled_transaction_message.instructions[message_instruction_length - 1] = ComputeBudgetProgram.setComputeUnitPrice({
435
+ microLamports: prioritization_fee
436
+ })
437
+
438
+ versionedMessage = decompiled_transaction_message.compileToV0Message(lookup_table_list);
439
+ await sleep(1000);
440
+ versionedMessage.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
441
+ versionedTransaction = new VersionedTransaction(versionedMessage);
442
+
443
+ versionedTransaction.feePayer = wallet.publicKey;
444
+ await sleep(3000);
445
+ let signedTransaction = await this.wativeCore.account.signTransaction(from, versionedTransaction, rpc_url);
446
+ let new_versioned_transaction = new VersionedTransaction(signedTransaction.output.message, signedTransaction.output.signatures);
447
+ let new_message = new_versioned_transaction.message.serialize();
448
+ new_message = Buffer.from(new_message).toString("hex");
449
+
450
+ let tx_hash = bs58.encode(signedTransaction.output.signatures[0]);
451
+ return {
452
+ status: true,
453
+ output: {
454
+ transactionHash: tx_hash,
455
+ message: new_message,
456
+ compute_units: simulate_transaction_result.value.unitsConsumed.toString()
457
+ }
458
+ }
459
+ }
460
+
461
+ private async sendSignedSolanaTransaction(from: string, signature: string, data: string, rpc_url: string) {
462
+ let signatures = [
463
+ bs58.decode(signature)
464
+ ];
465
+
466
+ let versionedMessage = VersionedMessage.deserialize(Uint8Array.from(Buffer.from(data, "hex")));
467
+ let versionedTransaction = new VersionedTransaction(versionedMessage, signatures);
468
+
469
+ return await this.wativeCore.account.sendSignedTransaction(from, versionedTransaction, rpc_url);
470
+ }
471
+
472
+ private async getPrioritizationFee(rpc_url: string) {
473
+ let program_id = new PublicKey("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4");
474
+ const connection = new Connection(rpc_url, "confirmed");
475
+ let signatures_info: any = await connection.getSignaturesForAddress(program_id, { limit: 5 });
476
+ await sleep(2000);
477
+
478
+ let signatures: any = [];
479
+ for (let i = 0; i < signatures_info.length; i++) {
480
+ signatures.push(signatures_info[i].signature);
481
+ }
482
+ let transaction: any = await connection.getParsedTransactions(signatures, {
483
+ maxSupportedTransactionVersion: 0,
484
+ });
485
+
486
+ let total_prioritization_fee = 0;
487
+ for (let i = 0; i < transaction.length; i++) {
488
+ let instructions_list = transaction[i].transaction.message.instructions;
489
+ for (let j = 0; j < instructions_list.length; j++) {
490
+ if (instructions_list[j].programId.toBase58() === "ComputeBudget111111111111111111111111111111") {
491
+ let data = instructions_list[j].data;
492
+ try {
493
+ let instruction = new TransactionInstruction({
494
+ keys: [],
495
+ programId: new PublicKey("ComputeBudget111111111111111111111111111111"),
496
+ data: Buffer.from(bs58.decode(data))
497
+ });
498
+
499
+ let { microLamports } = ComputeBudgetInstruction.decodeSetComputeUnitPrice(instruction)
500
+ total_prioritization_fee += Number(microLamports);
501
+ continue;
502
+ } catch (error) { }
503
+ }
504
+ }
505
+ }
506
+
507
+ return Math.ceil(total_prioritization_fee / 5)
508
+ }
509
+ }
510
+
511
+ export const sleep = (ms: number) => {
512
+ return new Promise((resolve) => setTimeout(resolve, ms));
513
+ }
514
+
515
+ export default WativeWielderServer;
package/src/tools.ts CHANGED
@@ -83,6 +83,13 @@ const sendEvmRawTransaction = async (chain_id: string, chain_rpc_url: string, ch
83
83
  return;
84
84
  }
85
85
 
86
+ let transfer_gas_limit: string;
87
+ if (chain_id === "42161" || chain_id === "421614") {
88
+ transfer_gas_limit = "1700000";
89
+ } else {
90
+ transfer_gas_limit = "21000";
91
+ }
92
+
86
93
  let data: string;
87
94
  let value: any;
88
95
  if (code_result.output === "0x") {
@@ -95,7 +102,7 @@ const sendEvmRawTransaction = async (chain_id: string, chain_rpc_url: string, ch
95
102
  }
96
103
  let account_balance = new BigNumber(account_balance_result.output);
97
104
  let tx_gas_price = new BigNumber(gasPrice);
98
- let max_value = account_balance.minus(tx_gas_price.multipliedBy(21000)).toFixed(0, BigNumber.ROUND_FLOOR);
105
+ let max_value = account_balance.minus(tx_gas_price.multipliedBy(Number(transfer_gas_limit))).toFixed(0, BigNumber.ROUND_FLOOR);
99
106
  let total_balance = (new BigNumber(max_value)).dividedBy(1e18).toFixed(4, BigNumber.ROUND_FLOOR);
100
107
 
101
108
  if (new BigNumber(total_balance) < new BigNumber(0)) {
@@ -127,7 +134,7 @@ const sendEvmRawTransaction = async (chain_id: string, chain_rpc_url: string, ch
127
134
  };
128
135
 
129
136
  if (data === '0x') {
130
- txParams['gas'] = "21000";
137
+ txParams['gas'] = transfer_gas_limit;
131
138
  } else {
132
139
  let sendGasPrice = txParams.gasPrice;
133
140
  delete txParams.gasPrice;
@@ -421,7 +428,11 @@ const sendEvmTokenTransaction = async (keystore_path: string, chain_rpc_url: str
421
428
  };
422
429
 
423
430
  if (data === '0x') {
424
- txParams['gas'] = "21000";
431
+ if (chain_id === "42161" || chain_id === "421614") {
432
+ txParams['gas'] = "1700000";
433
+ } else {
434
+ txParams['gas'] = "21000";
435
+ }
425
436
  } else {
426
437
  let sendGasPrice = txParams.gasPrice;
427
438
  delete txParams.gasPrice;
package/src/utils.ts CHANGED
@@ -428,6 +428,20 @@ const passwordValidator = function (value: string) {
428
428
  }
429
429
  };
430
430
 
431
+ export const inputMaskedPassword = async (text: string, validate_func?: Function) => {
432
+ const questions = [
433
+ {
434
+ name: 'inputText',
435
+ type: 'password',
436
+ mask: '#',
437
+ message: `${text}:`,
438
+ validate: validate_func
439
+ },
440
+ ]
441
+ const { inputText } = await inquirer.prompt(questions)
442
+ return inputText.trim().toString()
443
+ }
444
+
431
445
  export const inputPasswordWithoutValidator = async (text: string) => {
432
446
  const questions = [
433
447
  {