@everstake/wallet-sdk-hysp-solana 1.0.0

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/dist/index.mjs ADDED
@@ -0,0 +1,372 @@
1
+ // src/hysp.ts
2
+ import {
3
+ createSolanaRpc,
4
+ createNoopSigner,
5
+ address as address2,
6
+ pipe,
7
+ createTransactionMessage,
8
+ setTransactionMessageFeePayer,
9
+ setTransactionMessageLifetimeUsingBlockhash,
10
+ appendTransactionMessageInstruction,
11
+ prependTransactionMessageInstruction
12
+ } from "@solana/kit";
13
+ import {
14
+ getSetComputeUnitLimitInstruction,
15
+ getSetComputeUnitPriceInstruction
16
+ } from "@solana-program/compute-budget";
17
+ import { KaminoVault } from "@kamino-finance/klend-sdk";
18
+ import { Decimal } from "decimal.js";
19
+
20
+ // ../utils/constants/errors.ts
21
+ var COMMON_ERROR_MESSAGES = {
22
+ UNKNOWN_ERROR: "An unknown error occurred",
23
+ TOKEN_ERROR: "Please create or use correct token"
24
+ };
25
+
26
+ // ../utils/index.ts
27
+ var WalletSDKError = class extends Error {
28
+ constructor(message, code, originalError) {
29
+ super(message);
30
+ this.code = code;
31
+ this.originalError = originalError;
32
+ Object.setPrototypeOf(this, new.target.prototype);
33
+ }
34
+ };
35
+ var Blockchain = class {
36
+ /**
37
+ * Handles errors that occur within the Ethereum class.
38
+ *
39
+ * @param {keyof typeof ERROR_MESSAGES} code - The error code associated with the error.
40
+ * @param {Error | WalletSDKError | unknown} originalError - The original error that was thrown.
41
+ *
42
+ * If the original error is an instance of WalletSDKError, it is thrown as is.
43
+ * If the original error is an instance of the built-in Error class, a new WalletSDKError is thrown with the original error as the cause.
44
+ * If the original error is not an instance of WalletSDKError or Error, a new WalletSDKError is thrown with a generic message and code.
45
+ */
46
+ handleError(code, originalError) {
47
+ const message = this.ERROR_MESSAGES[code];
48
+ if (originalError instanceof WalletSDKError || !message || !code) {
49
+ throw originalError;
50
+ }
51
+ if (originalError instanceof Error) {
52
+ const newMessage = Object.entries(this.ORIGINAL_ERROR_MESSAGES).find(
53
+ ([originalMessage]) => originalError.message.includes(originalMessage)
54
+ )?.[1];
55
+ const errorMessage = newMessage || this.ERROR_MESSAGES[code] || COMMON_ERROR_MESSAGES["UNKNOWN_ERROR"];
56
+ throw new WalletSDKError(errorMessage, String(code), originalError);
57
+ }
58
+ throw new WalletSDKError(
59
+ COMMON_ERROR_MESSAGES["UNKNOWN_ERROR"],
60
+ "UNKNOWN_ERROR"
61
+ );
62
+ }
63
+ /**
64
+ * Throws a WalletSDKError with a specified error code and message.
65
+ *
66
+ * @param {keyof typeof ERROR_MESSAGES} code - The error code associated with the error.
67
+ * @param {...string[]} values - The values to be inserted into the error message.
68
+ *
69
+ * The method retrieves the error message template associated with the provided code from the ERROR_MESSAGES object.
70
+ * It then replaces placeholders in the message template with provided values and throws a WalletSDKError with the final message and the provided code.
71
+ */
72
+ throwError(code, ...values) {
73
+ let message = this.ERROR_MESSAGES[code];
74
+ values.forEach((value, index) => {
75
+ message = message?.replace(`{${index}}`, value);
76
+ });
77
+ if (!message) {
78
+ throw new WalletSDKError(
79
+ COMMON_ERROR_MESSAGES["UNKNOWN_ERROR"],
80
+ "UNKNOWN_ERROR"
81
+ );
82
+ }
83
+ throw new WalletSDKError(message, String(code));
84
+ }
85
+ /**
86
+ * Check if the URL is valid
87
+ *
88
+ * @param {string} url - URL
89
+ * @returns a bool type result.
90
+ *
91
+ */
92
+ isValidURL(url) {
93
+ let urlClass;
94
+ try {
95
+ urlClass = new URL(url);
96
+ } catch (_) {
97
+ return false;
98
+ }
99
+ return urlClass.protocol === "http:" || urlClass.protocol === "https:";
100
+ }
101
+ };
102
+
103
+ // src/constants/errors.ts
104
+ var ERROR_MESSAGES = {
105
+ INITIALIZATION_ERROR: "An error occurred while initializingSDK",
106
+ DEPOSIT_ERROR: "An error occurred while creating vault deposit transaction",
107
+ WITHDRAW_ERROR: "An error occurred while creating vault withdraw transaction",
108
+ GET_SHARES_ERROR: "An error occurred while fetching vault balance",
109
+ GET_BALANCE_ERROR: "An error occurred while fetching user token balance",
110
+ VAULT_LOAD_ERROR: "An error occurred while loading vault info",
111
+ VAULT_NOT_FOUND_ERROR: "Vault not found for token: {0}"
112
+ };
113
+
114
+ // src/constants/index.ts
115
+ import { address } from "@solana/kit";
116
+ var VAULTS = {
117
+ USDC: address("HDsayqAsDWy3QvANGqh2yNraqcD8Fnjgh73Mhb3WRS5E")
118
+ };
119
+
120
+ // src/hysp.ts
121
+ var HyspSolana = class extends Blockchain {
122
+ ERROR_MESSAGES = ERROR_MESSAGES;
123
+ ORIGINAL_ERROR_MESSAGES = ERROR_MESSAGES;
124
+ connection;
125
+ vault;
126
+ /**
127
+ * Creates a new instance of the KaminoSDK.
128
+ *
129
+ * @param rpcUrl - Optional custom RPC URL. If not provided, uses the default Solana mainnet RPC.
130
+ *
131
+ * @throws Throws an error if the token symbol is not supported.
132
+ * @throws Throws an error if there's an issue during SDK initialization.
133
+ */
134
+ constructor(tokenSymbol, rpcUrl) {
135
+ super();
136
+ const vaultAddress = this.getVaultAddress(tokenSymbol);
137
+ try {
138
+ const connectionUrl = rpcUrl || "https://api.mainnet-beta.solana.com";
139
+ this.connection = createSolanaRpc(connectionUrl);
140
+ this.vault = new KaminoVault(this.connection, vaultAddress);
141
+ } catch (error) {
142
+ throw this.handleError("INITIALIZATION_ERROR", error);
143
+ }
144
+ }
145
+ getVaultAddress(tokenSymbol) {
146
+ const vaultAddress = VAULTS[tokenSymbol];
147
+ if (!vaultAddress) {
148
+ throw this.throwError("VAULT_NOT_FOUND_ERROR", tokenSymbol);
149
+ }
150
+ return vaultAddress;
151
+ }
152
+ convertToDecimal(amount) {
153
+ return new Decimal(amount.toString());
154
+ }
155
+ /**
156
+ * Fetches the vault holdings data.
157
+ *
158
+ * @throws Throws an error if there's an issue loading vault holdings.
159
+ *
160
+ * @returns Returns a promise that resolves with the vault holdings.
161
+ */
162
+ async getVaultHoldings() {
163
+ try {
164
+ const holdings = await this.vault.getVaultHoldings();
165
+ return { result: holdings };
166
+ } catch (error) {
167
+ throw this.handleError("VAULT_LOAD_ERROR", error);
168
+ }
169
+ }
170
+ /**
171
+ * Fetches the vault APY data.
172
+ *
173
+ * @throws Throws an error if there's an issue loading vault APYs.
174
+ *
175
+ * @returns Returns a promise that resolves with the vault actual APY information.
176
+ */
177
+ async getVaultAPYs() {
178
+ try {
179
+ const apys = await this.vault.getAPYs();
180
+ return { result: apys.actualAPY };
181
+ } catch (error) {
182
+ throw this.handleError("VAULT_LOAD_ERROR", error);
183
+ }
184
+ }
185
+ /**
186
+ * Fetches the current vault exchange rate (tokens per share).
187
+ *
188
+ * @throws Throws an error if there's an issue loading exchange rate.
189
+ *
190
+ * @returns Returns a promise that resolves with the exchange rate as Decimal.
191
+ */
192
+ async getExchangeRate() {
193
+ try {
194
+ const rate = await this.vault.getExchangeRate();
195
+ return { result: rate };
196
+ } catch (error) {
197
+ throw this.handleError("VAULT_LOAD_ERROR", error);
198
+ }
199
+ }
200
+ /**
201
+ * Fetches a user's raw share balance in the current vault.
202
+ *
203
+ * @param userAddress - The public key of the user account.
204
+ * @throws Throws an error if there's an issue fetching user shares.
205
+ * @returns Returns a promise that resolves with the user's shares amount.
206
+ */
207
+ async getUserShares(userAddress) {
208
+ try {
209
+ const shares = await this.vault.getUserShares(userAddress);
210
+ return {
211
+ result: shares.totalShares
212
+ };
213
+ } catch (error) {
214
+ throw this.handleError("GET_SHARES_ERROR", error);
215
+ }
216
+ }
217
+ /**
218
+ * Fetches a user's token balance in the current vault: shares * exchange rate.
219
+ *
220
+ * @param userAddress - The public key of the user account.
221
+ * @throws Throws an error if there's an issue fetching user balance.
222
+ * @returns Returns a promise that resolves with the user's token balance amount.
223
+ */
224
+ async getUserBalance(userAddress) {
225
+ try {
226
+ const balance = await this.vault.getUserShares(userAddress);
227
+ const exchangeRate = await this.vault.getExchangeRate();
228
+ const tokenBalance = balance.totalShares.mul(exchangeRate);
229
+ return {
230
+ result: tokenBalance
231
+ };
232
+ } catch (error) {
233
+ throw this.handleError("GET_BALANCE_ERROR", error);
234
+ }
235
+ }
236
+ /**
237
+ * Creates a deposit transaction to the vault.
238
+ *
239
+ * @param userAddress - The public key of the user account.
240
+ * @param amount - The amount to deposit.
241
+ * @param params - Optional transaction parameters.
242
+ *
243
+ * @throws Throws an error if there's an issue creating the deposit transaction.
244
+ *
245
+ * @returns Returns a promise that resolves with the deposit transaction response.
246
+ */
247
+ async deposit(userAddress, amount, params) {
248
+ try {
249
+ const signer = createNoopSigner(userAddress);
250
+ let decimalAmount;
251
+ if (amount instanceof Decimal) {
252
+ decimalAmount = amount;
253
+ } else {
254
+ decimalAmount = this.convertToDecimal(amount);
255
+ }
256
+ const depositIxs = await this.vault.depositIxs(signer, decimalAmount);
257
+ const mergedDepositIxs = [];
258
+ depositIxs.depositIxs.forEach((instruction) => {
259
+ mergedDepositIxs.push(instruction);
260
+ });
261
+ depositIxs.stakeInFarmIfNeededIxs.forEach((instruction) => {
262
+ mergedDepositIxs.push(instruction);
263
+ });
264
+ const transactionMessage = await this.buildTx(
265
+ userAddress.toString(),
266
+ mergedDepositIxs,
267
+ params
268
+ );
269
+ return {
270
+ result: transactionMessage
271
+ };
272
+ } catch (error) {
273
+ throw this.handleError("DEPOSIT_ERROR", error);
274
+ }
275
+ }
276
+ /**
277
+ * Creates a withdraw transaction from the vault.
278
+ *
279
+ * @param userAddress - The public key of the user account.
280
+ * @param sharesAmount - The amount of shares to withdraw.
281
+ * @param params - Optional transaction parameters.
282
+ *
283
+ * @throws Throws an error if there's issue creating the withdraw transaction.
284
+ *
285
+ * @returns Returns a promise that resolves with the withdraw transaction response.
286
+ */
287
+ async withdraw(userAddress, sharesAmount, params) {
288
+ try {
289
+ const signer = createNoopSigner(userAddress);
290
+ let sharesDecimal;
291
+ if (sharesAmount instanceof Decimal) {
292
+ sharesDecimal = sharesAmount;
293
+ } else {
294
+ sharesDecimal = this.convertToDecimal(sharesAmount);
295
+ }
296
+ const withdrawIxs = await this.vault.withdrawIxs(signer, sharesDecimal);
297
+ const mergedWithdrawIxs = [];
298
+ withdrawIxs.unstakeFromFarmIfNeededIxs.forEach((instruction) => {
299
+ mergedWithdrawIxs.push(instruction);
300
+ });
301
+ withdrawIxs.withdrawIxs.forEach((instruction) => {
302
+ mergedWithdrawIxs.push(instruction);
303
+ });
304
+ withdrawIxs.postWithdrawIxs.forEach((instruction) => {
305
+ mergedWithdrawIxs.push(instruction);
306
+ });
307
+ const transactionMessage = await this.buildTx(
308
+ userAddress.toString(),
309
+ mergedWithdrawIxs,
310
+ params
311
+ );
312
+ return {
313
+ result: transactionMessage
314
+ };
315
+ } catch (error) {
316
+ throw this.handleError("WITHDRAW_ERROR", error);
317
+ }
318
+ }
319
+ async buildTx(sender, instructions, params) {
320
+ let transactionMessage = pipe(
321
+ createTransactionMessage({ version: 0 }),
322
+ (tx) => setTransactionMessageFeePayer(address2(sender), tx)
323
+ );
324
+ if (params?.computeUnitLimit !== void 0 && params?.computeUnitLimit > 0) {
325
+ const unitLimitInstruction = getSetComputeUnitLimitInstruction({
326
+ /** Transaction compute unit limit used for prioritization fees. */
327
+ units: params?.computeUnitLimit
328
+ });
329
+ transactionMessage = prependTransactionMessageInstruction(
330
+ unitLimitInstruction,
331
+ transactionMessage
332
+ );
333
+ }
334
+ if (params?.\u0441omputeUnitPrice !== void 0 && params?.\u0441omputeUnitPrice > 0) {
335
+ const unitPriceInstruction = getSetComputeUnitPriceInstruction({
336
+ /** Transaction compute unit price used for prioritization fees. */
337
+ microLamports: params?.\u0441omputeUnitPrice
338
+ });
339
+ transactionMessage = prependTransactionMessageInstruction(
340
+ unitPriceInstruction,
341
+ transactionMessage
342
+ );
343
+ }
344
+ for (const instruction of instructions) {
345
+ transactionMessage = appendTransactionMessageInstruction(
346
+ instruction,
347
+ transactionMessage
348
+ );
349
+ }
350
+ if (params?.afterInstructions) {
351
+ for (const instruction of params.afterInstructions) {
352
+ transactionMessage = appendTransactionMessageInstruction(
353
+ instruction,
354
+ transactionMessage
355
+ );
356
+ }
357
+ }
358
+ const finalLatestBlockhash = params?.finalLatestBlockhash || (await this.connection.getLatestBlockhash().send()).value;
359
+ const txMessageWithBlockhashLifetime = setTransactionMessageLifetimeUsingBlockhash(
360
+ finalLatestBlockhash,
361
+ transactionMessage
362
+ );
363
+ return txMessageWithBlockhashLifetime;
364
+ }
365
+ };
366
+ export {
367
+ Blockchain,
368
+ ERROR_MESSAGES,
369
+ HyspSolana,
370
+ VAULTS,
371
+ WalletSDKError
372
+ };
@@ -0,0 +1,21 @@
1
+ import globals from 'globals';
2
+ import pluginJs from '@eslint/js';
3
+ import tseslint from 'typescript-eslint';
4
+ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
5
+
6
+ export default [
7
+ { files: ['**/*.{js,mjs,cjs,ts}'] },
8
+ { languageOptions: { globals: globals.browser } },
9
+ pluginJs.configs.recommended,
10
+ ...tseslint.configs.strict,
11
+ eslintPluginPrettierRecommended,
12
+ {
13
+ rules: {
14
+ 'padding-line-between-statements': [
15
+ 'error',
16
+ { blankLine: 'always', prev: '*', next: 'return' },
17
+ ],
18
+ '@typescript-eslint/unified-signatures': 'off',
19
+ },
20
+ },
21
+ ];
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@everstake/wallet-sdk-hysp-solana",
3
+ "version": "1.0.0",
4
+ "description": "HYSP Solana - Everstake Wallet SDK",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.mjs",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "require": "./dist/index.js",
14
+ "import": "./dist/index.mjs"
15
+ }
16
+ },
17
+ "sideEffects": false,
18
+ "scripts": {
19
+ "build": "pnpm run prebuild && tsup src/index.ts --format cjs,esm --dts",
20
+ "type-check": "tsc",
21
+ "lint": "eslint 'src/**/*.{ts,tsx}'",
22
+ "prettier": "prettier --write 'src/**/*.{ts,tsx}'",
23
+ "test": "jest",
24
+ "prebuild": "pnpm run type-check && pnpm run lint"
25
+ },
26
+ "keywords": [
27
+ "hysp",
28
+ "solana",
29
+ "vault",
30
+ "blockchain",
31
+ "everstake",
32
+ "wallet sdk"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/everstake/wallet-sdk.git"
37
+ },
38
+ "author": "Everstake",
39
+ "license": "BSD-3-Clause",
40
+ "bugs": {
41
+ "url": "https://github.com/everstake/wallet-sdk/issues"
42
+ },
43
+ "homepage": "https://github.com/everstake/wallet-sdk#readme",
44
+ "dependencies": {
45
+ "@kamino-finance/klend-sdk": "^7.2.4",
46
+ "@solana-program/compute-budget": "^0.7.0",
47
+ "@solana/kit": "^3.0.3",
48
+ "decimal.js": "^10.6.0"
49
+ },
50
+ "devDependencies": {
51
+ "@eslint/js": "^9.7.0",
52
+ "@jest/types": "^29.6.3",
53
+ "@types/jest": "^29.5.12",
54
+ "@types/node": "^20.14.10",
55
+ "@typescript-eslint/eslint-plugin": "^7.16.0",
56
+ "@typescript-eslint/parser": "^7.16.0",
57
+ "eslint": "^9.7.0",
58
+ "eslint-config-prettier": "^9.1.0",
59
+ "eslint-plugin-prettier": "^5.1.3",
60
+ "globals": "^16.5.0",
61
+ "jest": "^29.7.0",
62
+ "prettier": "^3.3.2",
63
+ "ts-jest": "^29.2.2",
64
+ "ts-node": "^10.9.2",
65
+ "tsup": "^8.1.0",
66
+ "typescript": "^5.5.3",
67
+ "typescript-eslint": "^8.46.2"
68
+ }
69
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Copyright (c) 2025, Everstake.
3
+ * Licensed under the BSD-3-Clause License. See LICENSE file for details.
4
+ */
5
+
6
+ export const ERROR_MESSAGES = {
7
+ INITIALIZATION_ERROR: 'An error occurred while initializingSDK',
8
+ DEPOSIT_ERROR: 'An error occurred while creating vault deposit transaction',
9
+ WITHDRAW_ERROR: 'An error occurred while creating vault withdraw transaction',
10
+ GET_SHARES_ERROR: 'An error occurred while fetching vault balance',
11
+ GET_BALANCE_ERROR: 'An error occurred while fetching user token balance',
12
+ VAULT_LOAD_ERROR: 'An error occurred while loading vault info',
13
+ VAULT_NOT_FOUND_ERROR: 'Vault not found for token: {0}',
14
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) 2025, Everstake.
3
+ * Licensed under the BSD-3-Clause License. See LICENSE file for details.
4
+ */
5
+
6
+ import { address, Address } from '@solana/kit';
7
+
8
+ export type SupportedToken = 'USDC';
9
+
10
+ export type VaultsMap = {
11
+ [K in SupportedToken]: Address;
12
+ };
13
+
14
+ export const VAULTS: VaultsMap = {
15
+ USDC: address('HDsayqAsDWy3QvANGqh2yNraqcD8Fnjgh73Mhb3WRS5E'),
16
+ };
17
+
18
+ export * from './errors';