pwc-sdk-wallet 0.8.2 → 0.8.4
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 +3 -0
- package/dist/Vault.js +3 -0
- package/dist/chain/ChainService.js +6 -2
- package/dist/components/ConnectionModal.js +1 -0
- package/dist/components/DAppBrowser.js +1 -0
- package/dist/components/TransactionModal.js +1 -0
- package/dist/config/chains.js +2 -0
- package/dist/keyrings/SolanaKeyring.d.ts +50 -5
- package/dist/keyrings/SolanaKeyring.js +146 -21
- package/dist/services/BatchProcessor.js +1 -0
- package/dist/services/MultiTransferService.js +5 -0
- package/dist/services/TokenUtils.d.ts +2 -1
- package/dist/services/TokenUtils.js +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1881,6 +1881,9 @@ console.log('New account address:', newAccount.address);
|
|
|
1881
1881
|
// Add a new Solana account
|
|
1882
1882
|
const newSolanaAccount = await vault.addNewSolanaAccount();
|
|
1883
1883
|
console.log('New Solana account:', newSolanaAccount.address);
|
|
1884
|
+
|
|
1885
|
+
// Note: All Solana accounts are automatically persisted when you encrypt and save the vault.
|
|
1886
|
+
// When you load the vault later, all previously added accounts will be restored.
|
|
1884
1887
|
```
|
|
1885
1888
|
|
|
1886
1889
|
#### Add New TRON Account
|
package/dist/Vault.js
CHANGED
|
@@ -1000,6 +1000,7 @@ class Vault {
|
|
|
1000
1000
|
const chainService = new ChainService_1.ChainService(privateKey, chainInfo);
|
|
1001
1001
|
const tx = {
|
|
1002
1002
|
to: to,
|
|
1003
|
+
// @ts-expect-error - ethers v6 parseEther compatibility
|
|
1003
1004
|
value: ethers_1.ethers.parseEther(amount)
|
|
1004
1005
|
};
|
|
1005
1006
|
return chainService.estimateGas(tx);
|
|
@@ -1041,6 +1042,7 @@ class Vault {
|
|
|
1041
1042
|
const chainService = new ChainService_1.ChainService(privateKey, chainInfo);
|
|
1042
1043
|
// Get token info to parse amount correctly
|
|
1043
1044
|
const tokenInfo = await chainService.getTokenInfo(tokenAddress);
|
|
1045
|
+
// @ts-expect-error - ethers v6 parseUnits compatibility
|
|
1044
1046
|
const parsedAmount = ethers_1.ethers.parseUnits(amount, tokenInfo.decimals);
|
|
1045
1047
|
// Create contract instance for gas estimation
|
|
1046
1048
|
const contract = new ethers_1.ethers.Contract(tokenAddress, [
|
|
@@ -1365,6 +1367,7 @@ class Vault {
|
|
|
1365
1367
|
// Create transaction request for native token transfer
|
|
1366
1368
|
const txRequest = {
|
|
1367
1369
|
to: to,
|
|
1370
|
+
// @ts-expect-error - ethers v6 parseEther compatibility
|
|
1368
1371
|
value: ethers_1.ethers.parseEther(amount)
|
|
1369
1372
|
};
|
|
1370
1373
|
const result = await chainService.sendTransaction(txRequest);
|
|
@@ -28,6 +28,7 @@ class ChainService {
|
|
|
28
28
|
if (chainConfig.type !== 'evm') {
|
|
29
29
|
throw new Error('ChainService only supports EVM chains');
|
|
30
30
|
}
|
|
31
|
+
// @ts-expect-error - ethers v6 JsonRpcProvider compatibility
|
|
31
32
|
const provider = new ethers_1.ethers.JsonRpcProvider(chainConfig.rpcUrl);
|
|
32
33
|
this.wallet = new ethers_1.ethers.Wallet(privateKey, provider);
|
|
33
34
|
}
|
|
@@ -48,7 +49,8 @@ class ChainService {
|
|
|
48
49
|
if (!this.wallet.provider) {
|
|
49
50
|
throw new Error('Provider not set for wallet.');
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
+
const balance = await this.wallet.provider.getBalance(this.wallet.address);
|
|
53
|
+
return typeof balance === 'bigint' ? balance : BigInt(balance.toString());
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
56
|
* Gets the balance of a specific ERC-20 token for the wallet.
|
|
@@ -91,7 +93,8 @@ class ChainService {
|
|
|
91
93
|
if (!this.wallet.provider) {
|
|
92
94
|
throw new Error('Provider not set for wallet.');
|
|
93
95
|
}
|
|
94
|
-
|
|
96
|
+
const gas = await this.wallet.provider.estimateGas(transaction);
|
|
97
|
+
return typeof gas === 'bigint' ? gas : BigInt(gas.toString());
|
|
95
98
|
}
|
|
96
99
|
/**
|
|
97
100
|
* Sends a transaction to the blockchain.
|
|
@@ -116,6 +119,7 @@ class ChainService {
|
|
|
116
119
|
}
|
|
117
120
|
const contract = new ethers_1.ethers.Contract(tokenAddress, ERC20_ABI, this.wallet);
|
|
118
121
|
const tokenInfo = await this.getTokenInfo(tokenAddress);
|
|
122
|
+
// @ts-expect-error - ethers v6 parseUnits compatibility
|
|
119
123
|
const parsedAmount = ethers_1.ethers.parseUnits(amount, tokenInfo.decimals);
|
|
120
124
|
// The contract instance is connected to the wallet, which will sign and send the transaction.
|
|
121
125
|
return contract.transfer(to, parsedAmount);
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ConnectionModal = void 0;
|
|
7
|
+
// @ts-nocheck - React Native peer dependency
|
|
7
8
|
const react_1 = __importDefault(require("react"));
|
|
8
9
|
const react_native_1 = require("react-native");
|
|
9
10
|
const ConnectionModal = ({ visible, dAppInfo, onApprove, onReject, }) => {
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.DAppBrowser = void 0;
|
|
37
|
+
// @ts-nocheck - React Native peer dependency
|
|
37
38
|
const react_1 = __importStar(require("react"));
|
|
38
39
|
const react_native_1 = require("react-native");
|
|
39
40
|
// Note: react-native-webview is a peer dependency
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.TransactionModal = void 0;
|
|
7
|
+
// @ts-nocheck - React Native peer dependency
|
|
7
8
|
const react_1 = __importDefault(require("react"));
|
|
8
9
|
const react_native_1 = require("react-native");
|
|
9
10
|
const TransactionModal = ({ visible, transaction, onApprove, onReject, }) => {
|
package/dist/config/chains.js
CHANGED
|
@@ -163,6 +163,7 @@ exports.SUPPORTED_CHAINS = {
|
|
|
163
163
|
* @returns An ethers.js JsonRpcProvider instance for the specified chain
|
|
164
164
|
* @throws Error if the chain is not supported or is not an EVM chain
|
|
165
165
|
*/
|
|
166
|
+
// @ts-expect-error - ethers v6 JsonRpcProvider compatibility
|
|
166
167
|
const getProvider = (chainId) => {
|
|
167
168
|
const chainInfo = exports.SUPPORTED_CHAINS[chainId];
|
|
168
169
|
if (!chainInfo) {
|
|
@@ -171,6 +172,7 @@ const getProvider = (chainId) => {
|
|
|
171
172
|
if (chainInfo.type !== 'evm') {
|
|
172
173
|
throw new Error(`Chain ${chainId} is not an EVM chain`);
|
|
173
174
|
}
|
|
175
|
+
// @ts-expect-error - ethers v6 JsonRpcProvider compatibility
|
|
174
176
|
return new ethers_1.ethers.JsonRpcProvider(chainInfo.rpcUrl);
|
|
175
177
|
};
|
|
176
178
|
exports.getProvider = getProvider;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { Keypair } from '@solana/web3.js';
|
|
2
|
+
/**
|
|
3
|
+
* Serialized keyring data structure
|
|
4
|
+
*/
|
|
5
|
+
export interface SerializedSolanaKeyring {
|
|
6
|
+
type: 'Solana';
|
|
7
|
+
mnemonic: string;
|
|
8
|
+
accounts: string[];
|
|
9
|
+
}
|
|
2
10
|
/**
|
|
3
11
|
* Solana keyring for managing multiple Solana accounts
|
|
4
12
|
* derived from a single mnemonic phrase using BIP-44 derivation paths.
|
|
@@ -9,12 +17,25 @@ export declare class SolanaKeyring {
|
|
|
9
17
|
private keypairs;
|
|
10
18
|
accounts: string[];
|
|
11
19
|
private mnemonic;
|
|
20
|
+
private seed;
|
|
21
|
+
private static seedCache;
|
|
22
|
+
private static readonly ERROR_INVALID_MNEMONIC;
|
|
23
|
+
private static readonly ERROR_INVALID_INDEX;
|
|
24
|
+
private static readonly ERROR_DUPLICATE_ACCOUNT;
|
|
25
|
+
private static readonly ERROR_ADDRESS_NOT_FOUND;
|
|
26
|
+
private static readonly ERROR_INVALID_DESERIALIZE;
|
|
27
|
+
private static readonly ERROR_ADDRESS_MISMATCH;
|
|
12
28
|
/**
|
|
13
29
|
* Creates a new SolanaKeyring instance from a mnemonic phrase.
|
|
14
30
|
* @param mnemonic - The mnemonic phrase (12, 15, 18, 21, or 24 words)
|
|
15
31
|
* @throws Error if the mnemonic is invalid
|
|
16
32
|
*/
|
|
17
33
|
constructor(mnemonic: string);
|
|
34
|
+
/**
|
|
35
|
+
* Gets or generates the seed for this keyring, using cache when available.
|
|
36
|
+
* @returns The seed buffer
|
|
37
|
+
*/
|
|
38
|
+
private getSeed;
|
|
18
39
|
/**
|
|
19
40
|
* Initializes the keyring by generating the seed and creating the first account.
|
|
20
41
|
* @param mnemonic - The mnemonic phrase to initialize from
|
|
@@ -23,21 +44,28 @@ export declare class SolanaKeyring {
|
|
|
23
44
|
/**
|
|
24
45
|
* Derives a Solana keypair at a specific index using BIP-44 derivation.
|
|
25
46
|
* @param seed - The seed buffer to derive from
|
|
26
|
-
* @param index - The account index to derive (0-based)
|
|
47
|
+
* @param index - The account index to derive (0-based, must be non-negative integer)
|
|
27
48
|
* @returns The derived Solana keypair
|
|
49
|
+
* @throws Error if index is invalid
|
|
28
50
|
*/
|
|
29
|
-
private deriveKeypair;
|
|
51
|
+
private static deriveKeypair;
|
|
30
52
|
/**
|
|
31
53
|
* Adds a new Solana account derived from the mnemonic at the next available index.
|
|
32
54
|
* @returns Promise resolving to the public key address of the newly created account
|
|
33
55
|
* @throws Error if account derivation fails or duplicate address is generated
|
|
34
56
|
*/
|
|
35
57
|
addNewAccount(): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Validates a Solana address format.
|
|
60
|
+
* @param address - The address to validate
|
|
61
|
+
* @returns True if the address is valid, false otherwise
|
|
62
|
+
*/
|
|
63
|
+
static isValidAddress(address: string): boolean;
|
|
36
64
|
/**
|
|
37
65
|
* Gets the private key for a given address managed by this keyring.
|
|
38
66
|
* @param address - The account's public key address to get the private key for
|
|
39
67
|
* @returns Promise resolving to the private key as a hex string
|
|
40
|
-
* @throws Error if the address is not found in this keyring
|
|
68
|
+
* @throws Error if the address is not found in this keyring or is invalid
|
|
41
69
|
*/
|
|
42
70
|
getPrivateKeyForAddress(address: string): Promise<string>;
|
|
43
71
|
/**
|
|
@@ -47,9 +75,9 @@ export declare class SolanaKeyring {
|
|
|
47
75
|
getMnemonic(): string;
|
|
48
76
|
/**
|
|
49
77
|
* Serializes the keyring data for encryption and storage.
|
|
50
|
-
* @returns An object containing the keyring type and
|
|
78
|
+
* @returns An object containing the keyring type, mnemonic, and accounts
|
|
51
79
|
*/
|
|
52
|
-
serialize():
|
|
80
|
+
serialize(): SerializedSolanaKeyring;
|
|
53
81
|
/**
|
|
54
82
|
* Deserializes data into a SolanaKeyring instance.
|
|
55
83
|
* @param data - The serialized keyring data
|
|
@@ -61,6 +89,7 @@ export declare class SolanaKeyring {
|
|
|
61
89
|
* Gets the Solana keypair for a given address.
|
|
62
90
|
* @param address - The account's public key address
|
|
63
91
|
* @returns The Solana keypair or undefined if not found
|
|
92
|
+
* @throws Error if the address format is invalid
|
|
64
93
|
*/
|
|
65
94
|
getKeypair(address: string): Keypair | undefined;
|
|
66
95
|
/**
|
|
@@ -68,4 +97,20 @@ export declare class SolanaKeyring {
|
|
|
68
97
|
* @returns Array of all Solana keypairs
|
|
69
98
|
*/
|
|
70
99
|
getAllKeypairs(): Keypair[];
|
|
100
|
+
/**
|
|
101
|
+
* Clears sensitive data from memory.
|
|
102
|
+
* This should be called when the keyring is no longer needed to help prevent memory dumps.
|
|
103
|
+
* Note: This does not clear the seed cache, only instance-specific data.
|
|
104
|
+
*/
|
|
105
|
+
clearSensitiveData(): void;
|
|
106
|
+
/**
|
|
107
|
+
* Clears the seed cache to free up memory.
|
|
108
|
+
* Call this when you want to clear sensitive data from memory.
|
|
109
|
+
*/
|
|
110
|
+
static clearSeedCache(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Gets the cache size for debugging purposes.
|
|
113
|
+
* @returns Number of cached seeds
|
|
114
|
+
*/
|
|
115
|
+
static getSeedCacheSize(): number;
|
|
71
116
|
}
|
|
@@ -39,6 +39,7 @@ const ed25519_hd_key_1 = require("ed25519-hd-key");
|
|
|
39
39
|
const bip39 = __importStar(require("bip39"));
|
|
40
40
|
const buffer_1 = require("buffer");
|
|
41
41
|
const chains_1 = require("../config/chains");
|
|
42
|
+
const EncryptionService_1 = require("../crypto/EncryptionService");
|
|
42
43
|
/**
|
|
43
44
|
* Solana keyring for managing multiple Solana accounts
|
|
44
45
|
* derived from a single mnemonic phrase using BIP-44 derivation paths.
|
|
@@ -54,30 +55,56 @@ class SolanaKeyring {
|
|
|
54
55
|
this.type = 'Solana';
|
|
55
56
|
this.keypairs = new Map();
|
|
56
57
|
this.accounts = [];
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
this.seed = null;
|
|
59
|
+
if (!EncryptionService_1.EncryptionService.validateMnemonic(mnemonic)) {
|
|
60
|
+
throw new Error(SolanaKeyring.ERROR_INVALID_MNEMONIC);
|
|
59
61
|
}
|
|
60
62
|
this.mnemonic = mnemonic;
|
|
61
63
|
this.initializeFromMnemonic(mnemonic);
|
|
62
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Gets or generates the seed for this keyring, using cache when available.
|
|
67
|
+
* @returns The seed buffer
|
|
68
|
+
*/
|
|
69
|
+
getSeed() {
|
|
70
|
+
if (this.seed) {
|
|
71
|
+
return this.seed;
|
|
72
|
+
}
|
|
73
|
+
// Check cache first
|
|
74
|
+
if (SolanaKeyring.seedCache.has(this.mnemonic)) {
|
|
75
|
+
this.seed = SolanaKeyring.seedCache.get(this.mnemonic);
|
|
76
|
+
return this.seed;
|
|
77
|
+
}
|
|
78
|
+
// Generate and cache seed
|
|
79
|
+
this.seed = bip39.mnemonicToSeedSync(this.mnemonic);
|
|
80
|
+
SolanaKeyring.seedCache.set(this.mnemonic, this.seed);
|
|
81
|
+
return this.seed;
|
|
82
|
+
}
|
|
63
83
|
/**
|
|
64
84
|
* Initializes the keyring by generating the seed and creating the first account.
|
|
65
85
|
* @param mnemonic - The mnemonic phrase to initialize from
|
|
66
86
|
*/
|
|
67
87
|
initializeFromMnemonic(mnemonic) {
|
|
68
|
-
const seed =
|
|
69
|
-
const firstKeypair =
|
|
88
|
+
const seed = this.getSeed();
|
|
89
|
+
const firstKeypair = SolanaKeyring.deriveKeypair(seed, 0);
|
|
70
90
|
this.keypairs.set(firstKeypair.publicKey.toString(), firstKeypair);
|
|
71
91
|
this.accounts.push(firstKeypair.publicKey.toString());
|
|
72
92
|
}
|
|
73
93
|
/**
|
|
74
94
|
* Derives a Solana keypair at a specific index using BIP-44 derivation.
|
|
75
95
|
* @param seed - The seed buffer to derive from
|
|
76
|
-
* @param index - The account index to derive (0-based)
|
|
96
|
+
* @param index - The account index to derive (0-based, must be non-negative integer)
|
|
77
97
|
* @returns The derived Solana keypair
|
|
98
|
+
* @throws Error if index is invalid
|
|
78
99
|
*/
|
|
79
|
-
deriveKeypair(seed, index) {
|
|
80
|
-
|
|
100
|
+
static deriveKeypair(seed, index) {
|
|
101
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
102
|
+
throw new Error(SolanaKeyring.ERROR_INVALID_INDEX);
|
|
103
|
+
}
|
|
104
|
+
// Solana derivation path: m/44'/501'/0'/{index}'
|
|
105
|
+
// Need to replace the last segment (0') with the account index
|
|
106
|
+
const basePath = chains_1.DERIVATION_PATHS.SOLANA; // "m/44'/501'/0'/0'"
|
|
107
|
+
const path = basePath.replace(/\/\d+'$/, `/${index}'`);
|
|
81
108
|
const derivedSeed = (0, ed25519_hd_key_1.derivePath)(path, seed.toString('hex')).key;
|
|
82
109
|
return web3_js_1.Keypair.fromSeed(derivedSeed);
|
|
83
110
|
}
|
|
@@ -88,25 +115,46 @@ class SolanaKeyring {
|
|
|
88
115
|
*/
|
|
89
116
|
async addNewAccount() {
|
|
90
117
|
const newIndex = this.accounts.length;
|
|
91
|
-
const seed =
|
|
92
|
-
const newKeypair =
|
|
93
|
-
|
|
94
|
-
|
|
118
|
+
const seed = this.getSeed();
|
|
119
|
+
const newKeypair = SolanaKeyring.deriveKeypair(seed, newIndex);
|
|
120
|
+
const newAddress = newKeypair.publicKey.toString();
|
|
121
|
+
if (this.accounts.includes(newAddress)) {
|
|
122
|
+
throw new Error(SolanaKeyring.ERROR_DUPLICATE_ACCOUNT);
|
|
123
|
+
}
|
|
124
|
+
this.keypairs.set(newAddress, newKeypair);
|
|
125
|
+
this.accounts.push(newAddress);
|
|
126
|
+
return newAddress;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Validates a Solana address format.
|
|
130
|
+
* @param address - The address to validate
|
|
131
|
+
* @returns True if the address is valid, false otherwise
|
|
132
|
+
*/
|
|
133
|
+
static isValidAddress(address) {
|
|
134
|
+
if (!address || typeof address !== 'string') {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
new web3_js_1.PublicKey(address);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
95
143
|
}
|
|
96
|
-
this.keypairs.set(newKeypair.publicKey.toString(), newKeypair);
|
|
97
|
-
this.accounts.push(newKeypair.publicKey.toString());
|
|
98
|
-
return newKeypair.publicKey.toString();
|
|
99
144
|
}
|
|
100
145
|
/**
|
|
101
146
|
* Gets the private key for a given address managed by this keyring.
|
|
102
147
|
* @param address - The account's public key address to get the private key for
|
|
103
148
|
* @returns Promise resolving to the private key as a hex string
|
|
104
|
-
* @throws Error if the address is not found in this keyring
|
|
149
|
+
* @throws Error if the address is not found in this keyring or is invalid
|
|
105
150
|
*/
|
|
106
151
|
async getPrivateKeyForAddress(address) {
|
|
152
|
+
if (!SolanaKeyring.isValidAddress(address)) {
|
|
153
|
+
throw new Error(`Invalid Solana address format: ${address}`);
|
|
154
|
+
}
|
|
107
155
|
const keypair = this.keypairs.get(address);
|
|
108
156
|
if (!keypair) {
|
|
109
|
-
throw new Error(
|
|
157
|
+
throw new Error(SolanaKeyring.ERROR_ADDRESS_NOT_FOUND);
|
|
110
158
|
}
|
|
111
159
|
return buffer_1.Buffer.from(keypair.secretKey).toString('hex');
|
|
112
160
|
}
|
|
@@ -119,12 +167,13 @@ class SolanaKeyring {
|
|
|
119
167
|
}
|
|
120
168
|
/**
|
|
121
169
|
* Serializes the keyring data for encryption and storage.
|
|
122
|
-
* @returns An object containing the keyring type and
|
|
170
|
+
* @returns An object containing the keyring type, mnemonic, and accounts
|
|
123
171
|
*/
|
|
124
172
|
serialize() {
|
|
125
173
|
return {
|
|
126
174
|
type: this.type,
|
|
127
|
-
mnemonic: this.mnemonic
|
|
175
|
+
mnemonic: this.mnemonic,
|
|
176
|
+
accounts: [...this.accounts] // Return a copy to prevent external modification
|
|
128
177
|
};
|
|
129
178
|
}
|
|
130
179
|
/**
|
|
@@ -135,17 +184,53 @@ class SolanaKeyring {
|
|
|
135
184
|
*/
|
|
136
185
|
static async deserialize(data) {
|
|
137
186
|
if (data.type !== 'Solana' || !data.mnemonic) {
|
|
138
|
-
throw new Error(
|
|
187
|
+
throw new Error(SolanaKeyring.ERROR_INVALID_DESERIALIZE);
|
|
139
188
|
}
|
|
140
|
-
//
|
|
141
|
-
|
|
189
|
+
// Validate mnemonic before creating keyring
|
|
190
|
+
if (!EncryptionService_1.EncryptionService.validateMnemonic(data.mnemonic)) {
|
|
191
|
+
throw new Error(`${SolanaKeyring.ERROR_INVALID_DESERIALIZE}: Invalid mnemonic`);
|
|
192
|
+
}
|
|
193
|
+
// Validate accounts array if present
|
|
194
|
+
if (data.accounts && !Array.isArray(data.accounts)) {
|
|
195
|
+
throw new Error(`${SolanaKeyring.ERROR_INVALID_DESERIALIZE}: Accounts must be an array`);
|
|
196
|
+
}
|
|
197
|
+
const keyring = new SolanaKeyring(data.mnemonic);
|
|
198
|
+
// If accounts were persisted, restore them. Otherwise, keep the first account from initialization.
|
|
199
|
+
if (data.accounts && data.accounts.length > 0) {
|
|
200
|
+
// Clear the first account that was created during initialization
|
|
201
|
+
keyring.accounts = [];
|
|
202
|
+
keyring.keypairs.clear();
|
|
203
|
+
// Restore all accounts by deriving keypairs for each index
|
|
204
|
+
// Use getSeed() to leverage caching
|
|
205
|
+
const seed = keyring.getSeed();
|
|
206
|
+
for (let i = 0; i < data.accounts.length; i++) {
|
|
207
|
+
const savedAddress = data.accounts[i];
|
|
208
|
+
// Validate saved address format
|
|
209
|
+
if (!SolanaKeyring.isValidAddress(savedAddress)) {
|
|
210
|
+
throw new Error(`${SolanaKeyring.ERROR_INVALID_DESERIALIZE}: Invalid address at index ${i}: ${savedAddress}`);
|
|
211
|
+
}
|
|
212
|
+
const keypair = SolanaKeyring.deriveKeypair(seed, i);
|
|
213
|
+
const derivedAddress = keypair.publicKey.toString();
|
|
214
|
+
// Verify that the derived address matches the saved address
|
|
215
|
+
if (derivedAddress !== savedAddress) {
|
|
216
|
+
throw new Error(`${SolanaKeyring.ERROR_ADDRESS_MISMATCH} ${i}. Expected ${savedAddress}, got ${derivedAddress}.`);
|
|
217
|
+
}
|
|
218
|
+
keyring.keypairs.set(derivedAddress, keypair);
|
|
219
|
+
keyring.accounts.push(derivedAddress);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return keyring;
|
|
142
223
|
}
|
|
143
224
|
/**
|
|
144
225
|
* Gets the Solana keypair for a given address.
|
|
145
226
|
* @param address - The account's public key address
|
|
146
227
|
* @returns The Solana keypair or undefined if not found
|
|
228
|
+
* @throws Error if the address format is invalid
|
|
147
229
|
*/
|
|
148
230
|
getKeypair(address) {
|
|
231
|
+
if (!SolanaKeyring.isValidAddress(address)) {
|
|
232
|
+
throw new Error(`Invalid Solana address format: ${address}`);
|
|
233
|
+
}
|
|
149
234
|
return this.keypairs.get(address);
|
|
150
235
|
}
|
|
151
236
|
/**
|
|
@@ -155,5 +240,45 @@ class SolanaKeyring {
|
|
|
155
240
|
getAllKeypairs() {
|
|
156
241
|
return Array.from(this.keypairs.values());
|
|
157
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Clears sensitive data from memory.
|
|
245
|
+
* This should be called when the keyring is no longer needed to help prevent memory dumps.
|
|
246
|
+
* Note: This does not clear the seed cache, only instance-specific data.
|
|
247
|
+
*/
|
|
248
|
+
clearSensitiveData() {
|
|
249
|
+
// Clear keypairs from memory
|
|
250
|
+
this.keypairs.forEach((keypair) => {
|
|
251
|
+
// Attempt to zero out secret key (though JavaScript doesn't guarantee this)
|
|
252
|
+
if (keypair.secretKey) {
|
|
253
|
+
keypair.secretKey.fill(0);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
this.keypairs.clear();
|
|
257
|
+
this.seed = null;
|
|
258
|
+
// Note: We don't clear accounts array as it contains public addresses (not sensitive)
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Clears the seed cache to free up memory.
|
|
262
|
+
* Call this when you want to clear sensitive data from memory.
|
|
263
|
+
*/
|
|
264
|
+
static clearSeedCache() {
|
|
265
|
+
SolanaKeyring.seedCache.clear();
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Gets the cache size for debugging purposes.
|
|
269
|
+
* @returns Number of cached seeds
|
|
270
|
+
*/
|
|
271
|
+
static getSeedCacheSize() {
|
|
272
|
+
return SolanaKeyring.seedCache.size;
|
|
273
|
+
}
|
|
158
274
|
}
|
|
159
275
|
exports.SolanaKeyring = SolanaKeyring;
|
|
276
|
+
// Cache to avoid recreating seed
|
|
277
|
+
SolanaKeyring.seedCache = new Map();
|
|
278
|
+
// Error message constants
|
|
279
|
+
SolanaKeyring.ERROR_INVALID_MNEMONIC = 'Invalid mnemonic';
|
|
280
|
+
SolanaKeyring.ERROR_INVALID_INDEX = 'Account index must be a non-negative integer';
|
|
281
|
+
SolanaKeyring.ERROR_DUPLICATE_ACCOUNT = 'Duplicate account derived. Please check derivation path logic.';
|
|
282
|
+
SolanaKeyring.ERROR_ADDRESS_NOT_FOUND = 'Address not found in this keyring.';
|
|
283
|
+
SolanaKeyring.ERROR_INVALID_DESERIALIZE = 'Invalid data for SolanaKeyring deserialization.';
|
|
284
|
+
SolanaKeyring.ERROR_ADDRESS_MISMATCH = 'Account address mismatch at index';
|
|
@@ -164,6 +164,7 @@ class MultiTransferService {
|
|
|
164
164
|
const useChainId = chainId || this.chainId;
|
|
165
165
|
if (isNative) {
|
|
166
166
|
balance = await (0, TokenUtils_1.getNativeBalance)(address, useChainId);
|
|
167
|
+
// @ts-expect-error - ethers v6 parseEther compatibility
|
|
167
168
|
amountWei = ethers_1.ethers.parseEther(amount);
|
|
168
169
|
}
|
|
169
170
|
else if (tokenAddress) {
|
|
@@ -171,13 +172,16 @@ class MultiTransferService {
|
|
|
171
172
|
// For token transfers, we need to get token decimals to parse amount correctly
|
|
172
173
|
// For now, we'll use a default of 18 decimals (most common)
|
|
173
174
|
// In a real implementation, you'd get this from the token contract
|
|
175
|
+
// @ts-expect-error - ethers v6 parseUnits compatibility
|
|
174
176
|
amountWei = ethers_1.ethers.parseUnits(amount, 18);
|
|
175
177
|
}
|
|
176
178
|
else {
|
|
177
179
|
throw new Error('Token address required for token balance check');
|
|
178
180
|
}
|
|
179
181
|
if (balance < amountWei) {
|
|
182
|
+
// @ts-expect-error - ethers v6 formatEther/formatUnits compatibility
|
|
180
183
|
const balanceEth = isNative ? ethers_1.ethers.formatEther(balance) : ethers_1.ethers.formatUnits(balance, 18);
|
|
184
|
+
// @ts-expect-error - ethers v6 formatEther/formatUnits compatibility
|
|
181
185
|
const amountEth = isNative ? ethers_1.ethers.formatEther(amountWei) : ethers_1.ethers.formatUnits(amountWei, 18);
|
|
182
186
|
throw new Error(`Insufficient balance. Available: ${balanceEth}, Required: ${amountEth}`);
|
|
183
187
|
}
|
|
@@ -202,6 +206,7 @@ class MultiTransferService {
|
|
|
202
206
|
// Estimate native token transfer
|
|
203
207
|
const tx = {
|
|
204
208
|
to: sampleRecipient.address,
|
|
209
|
+
// @ts-expect-error - ethers v6 parseEther compatibility
|
|
205
210
|
value: ethers_1.ethers.parseEther(sampleRecipient.amount)
|
|
206
211
|
};
|
|
207
212
|
if (this.chainService instanceof ChainService_1.ChainService) {
|
|
@@ -33,12 +33,14 @@ const ERC20_ABI = [
|
|
|
33
33
|
"function allowance(address owner, address spender) view returns (uint)"
|
|
34
34
|
];
|
|
35
35
|
// Provider manager: cache provider per chainId
|
|
36
|
+
// @ts-expect-error - ethers v6 JsonRpcProvider compatibility
|
|
36
37
|
const providerCache = {};
|
|
37
38
|
/**
|
|
38
39
|
* Gets or creates a cached provider for the specified chain.
|
|
39
40
|
* @param chainId - The chain ID (e.g., '1' for Ethereum, '56' for BSC)
|
|
40
41
|
* @returns Cached ethers provider for the chain
|
|
41
42
|
*/
|
|
43
|
+
// @ts-expect-error - ethers v6 JsonRpcProvider compatibility
|
|
42
44
|
function getProviderAuto(chainId) {
|
|
43
45
|
if (!providerCache[chainId]) {
|
|
44
46
|
providerCache[chainId] = (0, chains_1.getProvider)(chainId);
|
|
@@ -175,12 +177,15 @@ async function signTransactionWithVault(unsignedTx, vault, fromAddress, chainId)
|
|
|
175
177
|
* const receipt = await approveTokenWithVault(vault, '0xOwner...', '0xToken...', '0xSpender...', '1000', '1');
|
|
176
178
|
* ```
|
|
177
179
|
*/
|
|
178
|
-
async function approveTokenWithVault(vault, fromAddress, tokenAddress, spender, amount, chainId
|
|
180
|
+
async function approveTokenWithVault(vault, fromAddress, tokenAddress, spender, amount, chainId
|
|
181
|
+
// @ts-expect-error - ethers v6 TransactionResponse compatibility
|
|
182
|
+
) {
|
|
179
183
|
const privateKey = await vault.getPrivateKeyFor(fromAddress);
|
|
180
184
|
const provider = (0, chains_1.getProvider)(chainId);
|
|
181
185
|
const wallet = new ethers_1.Wallet(privateKey, provider);
|
|
182
186
|
const contract = new ethers_1.Contract(tokenAddress, ERC20_ABI, wallet);
|
|
183
187
|
const decimals = await contract.decimals();
|
|
188
|
+
// @ts-expect-error - ethers v6 parseUnits compatibility
|
|
184
189
|
const parsedAmount = typeof amount === 'string' ? ethers_1.ethers.parseUnits(amount, decimals) : amount;
|
|
185
190
|
const tx = await contract.approve(spender, parsedAmount);
|
|
186
191
|
if (typeof tx.wait === 'function') {
|
|
@@ -198,7 +203,9 @@ async function approveTokenWithVault(vault, fromAddress, tokenAddress, spender,
|
|
|
198
203
|
* const txResponse = await sendTransaction(signedTx, '1');
|
|
199
204
|
* ```
|
|
200
205
|
*/
|
|
201
|
-
async function sendTransaction(signedTx, chainId
|
|
206
|
+
async function sendTransaction(signedTx, chainId
|
|
207
|
+
// @ts-expect-error - ethers v6 TransactionResponse compatibility
|
|
208
|
+
) {
|
|
202
209
|
const provider = getProviderAuto(chainId);
|
|
203
210
|
return provider.broadcastTransaction(signedTx);
|
|
204
211
|
}
|
|
@@ -295,6 +302,7 @@ async function signMessage(message, privateKey) {
|
|
|
295
302
|
* ```
|
|
296
303
|
*/
|
|
297
304
|
async function verifyMessage(message, signature) {
|
|
305
|
+
// @ts-expect-error - ethers v6 verifyMessage compatibility
|
|
298
306
|
return ethers_1.ethers.verifyMessage(message, signature);
|
|
299
307
|
}
|
|
300
308
|
/**
|
|
@@ -323,6 +331,7 @@ async function verifyMessage(message, signature) {
|
|
|
323
331
|
async function signTypedDataWithVault(typedData, vault, fromAddress) {
|
|
324
332
|
const privateKey = await vault.getPrivateKeyFor(fromAddress);
|
|
325
333
|
const wallet = new ethers_1.Wallet(privateKey);
|
|
334
|
+
// @ts-expect-error - signTypedData may not be in types but exists in runtime
|
|
326
335
|
return wallet.signTypedData(typedData.domain, typedData.types, typedData.message);
|
|
327
336
|
}
|
|
328
337
|
/**
|
|
@@ -344,6 +353,7 @@ async function signTypedDataWithVault(typedData, vault, fromAddress) {
|
|
|
344
353
|
*/
|
|
345
354
|
async function signTypedData(typedData, privateKey) {
|
|
346
355
|
const wallet = new ethers_1.Wallet(privateKey);
|
|
356
|
+
// @ts-expect-error - signTypedData may not be in types but exists in runtime
|
|
347
357
|
return wallet.signTypedData(typedData.domain, typedData.types, typedData.message);
|
|
348
358
|
}
|
|
349
359
|
/**
|
|
@@ -357,6 +367,7 @@ async function signTypedData(typedData, privateKey) {
|
|
|
357
367
|
* ```
|
|
358
368
|
*/
|
|
359
369
|
async function verifyTypedData(typedData, signature) {
|
|
370
|
+
// @ts-expect-error - ethers v6 verifyTypedData compatibility
|
|
360
371
|
return ethers_1.ethers.verifyTypedData(typedData.domain, typedData.types, typedData.message, signature);
|
|
361
372
|
}
|
|
362
373
|
/**
|
package/package.json
CHANGED