@everstake/wallet-sdk-hysp-solana 1.4.0 → 1.5.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.d.mts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +31 -22
- package/dist/index.mjs +33 -24
- package/jest.config.ts +2 -1
- package/package.json +13 -3
- package/src/__tests__/processMemo.test.ts +146 -0
- package/src/__tests__/transaction-size.test.ts +118 -0
- package/src/constants/errors.ts +6 -0
- package/src/constants/index.ts +2 -0
- package/src/hysp.ts +33 -23
- package/src/__tests__/index.ts +0 -41
package/dist/index.d.mts
CHANGED
|
@@ -88,6 +88,10 @@ declare const ERROR_MESSAGES: {
|
|
|
88
88
|
GET_BALANCE_ERROR: string;
|
|
89
89
|
VAULT_LOAD_ERROR: string;
|
|
90
90
|
VAULT_NOT_FOUND_ERROR: string;
|
|
91
|
+
TX_TOO_LARGE: string;
|
|
92
|
+
MEMO_REQUIRED_ERROR: string;
|
|
93
|
+
MEMO_TOO_LONG_ERROR: string;
|
|
94
|
+
MEMO_INVALID_CHARACTERS_ERROR: string;
|
|
91
95
|
};
|
|
92
96
|
|
|
93
97
|
/**
|
|
@@ -106,6 +110,7 @@ type VaultsMap = {
|
|
|
106
110
|
[K in SupportedToken]: VaultInfo;
|
|
107
111
|
};
|
|
108
112
|
declare const VAULTS: VaultsMap;
|
|
113
|
+
declare const MAX_TRANSACTION_SIZE = 1232;
|
|
109
114
|
|
|
110
115
|
/**
|
|
111
116
|
* Copyright (c) 2025, Everstake.
|
|
@@ -170,6 +175,10 @@ declare class HyspSolana extends Blockchain {
|
|
|
170
175
|
GET_BALANCE_ERROR: string;
|
|
171
176
|
VAULT_LOAD_ERROR: string;
|
|
172
177
|
VAULT_NOT_FOUND_ERROR: string;
|
|
178
|
+
TX_TOO_LARGE: string;
|
|
179
|
+
MEMO_REQUIRED_ERROR: string;
|
|
180
|
+
MEMO_TOO_LONG_ERROR: string;
|
|
181
|
+
MEMO_INVALID_CHARACTERS_ERROR: string;
|
|
173
182
|
};
|
|
174
183
|
protected ORIGINAL_ERROR_MESSAGES: {
|
|
175
184
|
INITIALIZATION_ERROR: string;
|
|
@@ -179,6 +188,10 @@ declare class HyspSolana extends Blockchain {
|
|
|
179
188
|
GET_BALANCE_ERROR: string;
|
|
180
189
|
VAULT_LOAD_ERROR: string;
|
|
181
190
|
VAULT_NOT_FOUND_ERROR: string;
|
|
191
|
+
TX_TOO_LARGE: string;
|
|
192
|
+
MEMO_REQUIRED_ERROR: string;
|
|
193
|
+
MEMO_TOO_LONG_ERROR: string;
|
|
194
|
+
MEMO_INVALID_CHARACTERS_ERROR: string;
|
|
182
195
|
};
|
|
183
196
|
private connection;
|
|
184
197
|
private vault;
|
|
@@ -282,4 +295,4 @@ declare class HyspSolana extends Blockchain {
|
|
|
282
295
|
private buildTx;
|
|
283
296
|
}
|
|
284
297
|
|
|
285
|
-
export { type ApiResponse, Blockchain, ERROR_MESSAGES, HyspSolana, type Params, type ShareToken, type SupportedToken, VAULTS, type VaultContracts, type VaultInfo, type VaultMeta, type VaultsMap, WalletSDKError };
|
|
298
|
+
export { type ApiResponse, Blockchain, ERROR_MESSAGES, HyspSolana, MAX_TRANSACTION_SIZE, type Params, type ShareToken, type SupportedToken, VAULTS, type VaultContracts, type VaultInfo, type VaultMeta, type VaultsMap, WalletSDKError };
|
package/dist/index.d.ts
CHANGED
|
@@ -88,6 +88,10 @@ declare const ERROR_MESSAGES: {
|
|
|
88
88
|
GET_BALANCE_ERROR: string;
|
|
89
89
|
VAULT_LOAD_ERROR: string;
|
|
90
90
|
VAULT_NOT_FOUND_ERROR: string;
|
|
91
|
+
TX_TOO_LARGE: string;
|
|
92
|
+
MEMO_REQUIRED_ERROR: string;
|
|
93
|
+
MEMO_TOO_LONG_ERROR: string;
|
|
94
|
+
MEMO_INVALID_CHARACTERS_ERROR: string;
|
|
91
95
|
};
|
|
92
96
|
|
|
93
97
|
/**
|
|
@@ -106,6 +110,7 @@ type VaultsMap = {
|
|
|
106
110
|
[K in SupportedToken]: VaultInfo;
|
|
107
111
|
};
|
|
108
112
|
declare const VAULTS: VaultsMap;
|
|
113
|
+
declare const MAX_TRANSACTION_SIZE = 1232;
|
|
109
114
|
|
|
110
115
|
/**
|
|
111
116
|
* Copyright (c) 2025, Everstake.
|
|
@@ -170,6 +175,10 @@ declare class HyspSolana extends Blockchain {
|
|
|
170
175
|
GET_BALANCE_ERROR: string;
|
|
171
176
|
VAULT_LOAD_ERROR: string;
|
|
172
177
|
VAULT_NOT_FOUND_ERROR: string;
|
|
178
|
+
TX_TOO_LARGE: string;
|
|
179
|
+
MEMO_REQUIRED_ERROR: string;
|
|
180
|
+
MEMO_TOO_LONG_ERROR: string;
|
|
181
|
+
MEMO_INVALID_CHARACTERS_ERROR: string;
|
|
173
182
|
};
|
|
174
183
|
protected ORIGINAL_ERROR_MESSAGES: {
|
|
175
184
|
INITIALIZATION_ERROR: string;
|
|
@@ -179,6 +188,10 @@ declare class HyspSolana extends Blockchain {
|
|
|
179
188
|
GET_BALANCE_ERROR: string;
|
|
180
189
|
VAULT_LOAD_ERROR: string;
|
|
181
190
|
VAULT_NOT_FOUND_ERROR: string;
|
|
191
|
+
TX_TOO_LARGE: string;
|
|
192
|
+
MEMO_REQUIRED_ERROR: string;
|
|
193
|
+
MEMO_TOO_LONG_ERROR: string;
|
|
194
|
+
MEMO_INVALID_CHARACTERS_ERROR: string;
|
|
182
195
|
};
|
|
183
196
|
private connection;
|
|
184
197
|
private vault;
|
|
@@ -282,4 +295,4 @@ declare class HyspSolana extends Blockchain {
|
|
|
282
295
|
private buildTx;
|
|
283
296
|
}
|
|
284
297
|
|
|
285
|
-
export { type ApiResponse, Blockchain, ERROR_MESSAGES, HyspSolana, type Params, type ShareToken, type SupportedToken, VAULTS, type VaultContracts, type VaultInfo, type VaultMeta, type VaultsMap, WalletSDKError };
|
|
298
|
+
export { type ApiResponse, Blockchain, ERROR_MESSAGES, HyspSolana, MAX_TRANSACTION_SIZE, type Params, type ShareToken, type SupportedToken, VAULTS, type VaultContracts, type VaultInfo, type VaultMeta, type VaultsMap, WalletSDKError };
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
Blockchain: () => Blockchain,
|
|
24
24
|
ERROR_MESSAGES: () => ERROR_MESSAGES,
|
|
25
25
|
HyspSolana: () => HyspSolana,
|
|
26
|
+
MAX_TRANSACTION_SIZE: () => MAX_TRANSACTION_SIZE,
|
|
26
27
|
VAULTS: () => VAULTS,
|
|
27
28
|
WalletSDKError: () => WalletSDKError
|
|
28
29
|
});
|
|
@@ -126,7 +127,11 @@ var ERROR_MESSAGES = {
|
|
|
126
127
|
GET_SHARES_ERROR: "An error occurred while fetching vault balance",
|
|
127
128
|
GET_BALANCE_ERROR: "An error occurred while fetching user token balance",
|
|
128
129
|
VAULT_LOAD_ERROR: "An error occurred while loading vault info",
|
|
129
|
-
VAULT_NOT_FOUND_ERROR: "Vault not found for token: {0}"
|
|
130
|
+
VAULT_NOT_FOUND_ERROR: "Vault not found for token: {0}",
|
|
131
|
+
TX_TOO_LARGE: "Transaction exceeds the maximum size limit of 1232 bytes",
|
|
132
|
+
MEMO_REQUIRED_ERROR: "Memo is required. Please contact us to get your referrer ID.",
|
|
133
|
+
MEMO_TOO_LONG_ERROR: 'Invalid memo: "{0}". Must be max 64 characters',
|
|
134
|
+
MEMO_INVALID_CHARACTERS_ERROR: 'Invalid memo: "{0}". Must contain only [A-Za-z0-9:_-] characters'
|
|
130
135
|
};
|
|
131
136
|
|
|
132
137
|
// src/constants/index.ts
|
|
@@ -140,6 +145,7 @@ var VAULTS = {
|
|
|
140
145
|
shareTokenDecimals: 6
|
|
141
146
|
}
|
|
142
147
|
};
|
|
148
|
+
var MAX_TRANSACTION_SIZE = 1232;
|
|
143
149
|
|
|
144
150
|
// src/hysp.ts
|
|
145
151
|
var HyspSolana = class extends Blockchain {
|
|
@@ -400,37 +406,29 @@ var HyspSolana = class extends Blockchain {
|
|
|
400
406
|
* @returns Memo instruction
|
|
401
407
|
*/
|
|
402
408
|
processMemo(memo) {
|
|
403
|
-
let processedMemo;
|
|
404
409
|
if (!memo || memo.trim() === "") {
|
|
405
|
-
|
|
410
|
+
throw this.throwError("MEMO_REQUIRED_ERROR");
|
|
411
|
+
}
|
|
412
|
+
const trimmedMemo = memo.trim();
|
|
413
|
+
let processedMemo;
|
|
414
|
+
if (!trimmedMemo.startsWith("SDK")) {
|
|
415
|
+
processedMemo = `SDK:${trimmedMemo}`;
|
|
406
416
|
} else {
|
|
407
|
-
|
|
408
|
-
if (trimmedMemo === "SDK") {
|
|
409
|
-
processedMemo = trimmedMemo;
|
|
410
|
-
} else if (!trimmedMemo.startsWith("SDK:")) {
|
|
411
|
-
processedMemo = `SDK:${trimmedMemo}`;
|
|
412
|
-
} else {
|
|
413
|
-
processedMemo = trimmedMemo;
|
|
414
|
-
}
|
|
417
|
+
processedMemo = trimmedMemo;
|
|
415
418
|
}
|
|
416
419
|
if (processedMemo.length > 64) {
|
|
417
|
-
throw
|
|
418
|
-
`Invalid memo: "${processedMemo}". Must be max 64 characters`
|
|
419
|
-
);
|
|
420
|
+
throw this.throwError("MEMO_TOO_LONG_ERROR", processedMemo);
|
|
420
421
|
}
|
|
421
422
|
const validPattern = /^[A-Za-z0-9:_-]*$/;
|
|
422
423
|
if (!validPattern.test(processedMemo)) {
|
|
423
|
-
throw
|
|
424
|
-
`Invalid memo: "${processedMemo}". Must contain only [A-Za-z0-9:_-] characters`
|
|
425
|
-
);
|
|
424
|
+
throw this.throwError("MEMO_INVALID_CHARACTERS_ERROR", processedMemo);
|
|
426
425
|
}
|
|
427
426
|
return (0, import_memo.getAddMemoInstruction)({ memo: processedMemo });
|
|
428
427
|
}
|
|
429
428
|
async buildTx(sender, instructions, params, lookupTableAddresses) {
|
|
430
|
-
let transactionMessage = (0, import_kit2.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
);
|
|
429
|
+
let transactionMessage = (0, import_kit2.createTransactionMessage)({
|
|
430
|
+
version: 0
|
|
431
|
+
});
|
|
434
432
|
if (params?.computeUnitLimit !== void 0 && params?.computeUnitLimit > 0) {
|
|
435
433
|
const unitLimitInstruction = (0, import_compute_budget.getSetComputeUnitLimitInstruction)({
|
|
436
434
|
/** Transaction compute unit limit used for prioritization fees. */
|
|
@@ -482,11 +480,21 @@ var HyspSolana = class extends Blockchain {
|
|
|
482
480
|
fetchedTables
|
|
483
481
|
);
|
|
484
482
|
}
|
|
483
|
+
const txWithFeePayer = (0, import_kit2.setTransactionMessageFeePayer)(
|
|
484
|
+
(0, import_kit2.address)(sender),
|
|
485
|
+
transactionMessage
|
|
486
|
+
);
|
|
485
487
|
const finalLatestBlockhash = params?.finalLatestBlockhash || (await this.connection.getLatestBlockhash().send()).value;
|
|
486
488
|
const txMessageWithBlockhashLifetime = (0, import_kit2.setTransactionMessageLifetimeUsingBlockhash)(
|
|
487
489
|
finalLatestBlockhash,
|
|
488
|
-
|
|
490
|
+
txWithFeePayer
|
|
489
491
|
);
|
|
492
|
+
const compiledTx = (0, import_kit2.compileTransaction)(txMessageWithBlockhashLifetime);
|
|
493
|
+
const serializedTx = (0, import_kit2.getTransactionEncoder)().encode(compiledTx);
|
|
494
|
+
const txSize = serializedTx.length;
|
|
495
|
+
if (txSize > MAX_TRANSACTION_SIZE) {
|
|
496
|
+
throw this.throwError("TX_TOO_LARGE");
|
|
497
|
+
}
|
|
490
498
|
return txMessageWithBlockhashLifetime;
|
|
491
499
|
}
|
|
492
500
|
};
|
|
@@ -495,6 +503,7 @@ var HyspSolana = class extends Blockchain {
|
|
|
495
503
|
Blockchain,
|
|
496
504
|
ERROR_MESSAGES,
|
|
497
505
|
HyspSolana,
|
|
506
|
+
MAX_TRANSACTION_SIZE,
|
|
498
507
|
VAULTS,
|
|
499
508
|
WalletSDKError
|
|
500
509
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -3,14 +3,15 @@ import {
|
|
|
3
3
|
createSolanaRpc,
|
|
4
4
|
createNoopSigner,
|
|
5
5
|
address as address2,
|
|
6
|
-
pipe,
|
|
7
6
|
createTransactionMessage,
|
|
8
7
|
setTransactionMessageFeePayer,
|
|
9
8
|
setTransactionMessageLifetimeUsingBlockhash,
|
|
10
9
|
appendTransactionMessageInstruction,
|
|
11
10
|
prependTransactionMessageInstruction,
|
|
12
11
|
compressTransactionMessageUsingAddressLookupTables,
|
|
13
|
-
fetchAddressesForLookupTables
|
|
12
|
+
fetchAddressesForLookupTables,
|
|
13
|
+
compileTransaction,
|
|
14
|
+
getTransactionEncoder
|
|
14
15
|
} from "@solana/kit";
|
|
15
16
|
import {
|
|
16
17
|
getSetComputeUnitLimitInstruction,
|
|
@@ -111,7 +112,11 @@ var ERROR_MESSAGES = {
|
|
|
111
112
|
GET_SHARES_ERROR: "An error occurred while fetching vault balance",
|
|
112
113
|
GET_BALANCE_ERROR: "An error occurred while fetching user token balance",
|
|
113
114
|
VAULT_LOAD_ERROR: "An error occurred while loading vault info",
|
|
114
|
-
VAULT_NOT_FOUND_ERROR: "Vault not found for token: {0}"
|
|
115
|
+
VAULT_NOT_FOUND_ERROR: "Vault not found for token: {0}",
|
|
116
|
+
TX_TOO_LARGE: "Transaction exceeds the maximum size limit of 1232 bytes",
|
|
117
|
+
MEMO_REQUIRED_ERROR: "Memo is required. Please contact us to get your referrer ID.",
|
|
118
|
+
MEMO_TOO_LONG_ERROR: 'Invalid memo: "{0}". Must be max 64 characters',
|
|
119
|
+
MEMO_INVALID_CHARACTERS_ERROR: 'Invalid memo: "{0}". Must contain only [A-Za-z0-9:_-] characters'
|
|
115
120
|
};
|
|
116
121
|
|
|
117
122
|
// src/constants/index.ts
|
|
@@ -125,6 +130,7 @@ var VAULTS = {
|
|
|
125
130
|
shareTokenDecimals: 6
|
|
126
131
|
}
|
|
127
132
|
};
|
|
133
|
+
var MAX_TRANSACTION_SIZE = 1232;
|
|
128
134
|
|
|
129
135
|
// src/hysp.ts
|
|
130
136
|
var HyspSolana = class extends Blockchain {
|
|
@@ -385,37 +391,29 @@ var HyspSolana = class extends Blockchain {
|
|
|
385
391
|
* @returns Memo instruction
|
|
386
392
|
*/
|
|
387
393
|
processMemo(memo) {
|
|
388
|
-
let processedMemo;
|
|
389
394
|
if (!memo || memo.trim() === "") {
|
|
390
|
-
|
|
395
|
+
throw this.throwError("MEMO_REQUIRED_ERROR");
|
|
396
|
+
}
|
|
397
|
+
const trimmedMemo = memo.trim();
|
|
398
|
+
let processedMemo;
|
|
399
|
+
if (!trimmedMemo.startsWith("SDK")) {
|
|
400
|
+
processedMemo = `SDK:${trimmedMemo}`;
|
|
391
401
|
} else {
|
|
392
|
-
|
|
393
|
-
if (trimmedMemo === "SDK") {
|
|
394
|
-
processedMemo = trimmedMemo;
|
|
395
|
-
} else if (!trimmedMemo.startsWith("SDK:")) {
|
|
396
|
-
processedMemo = `SDK:${trimmedMemo}`;
|
|
397
|
-
} else {
|
|
398
|
-
processedMemo = trimmedMemo;
|
|
399
|
-
}
|
|
402
|
+
processedMemo = trimmedMemo;
|
|
400
403
|
}
|
|
401
404
|
if (processedMemo.length > 64) {
|
|
402
|
-
throw
|
|
403
|
-
`Invalid memo: "${processedMemo}". Must be max 64 characters`
|
|
404
|
-
);
|
|
405
|
+
throw this.throwError("MEMO_TOO_LONG_ERROR", processedMemo);
|
|
405
406
|
}
|
|
406
407
|
const validPattern = /^[A-Za-z0-9:_-]*$/;
|
|
407
408
|
if (!validPattern.test(processedMemo)) {
|
|
408
|
-
throw
|
|
409
|
-
`Invalid memo: "${processedMemo}". Must contain only [A-Za-z0-9:_-] characters`
|
|
410
|
-
);
|
|
409
|
+
throw this.throwError("MEMO_INVALID_CHARACTERS_ERROR", processedMemo);
|
|
411
410
|
}
|
|
412
411
|
return getAddMemoInstruction({ memo: processedMemo });
|
|
413
412
|
}
|
|
414
413
|
async buildTx(sender, instructions, params, lookupTableAddresses) {
|
|
415
|
-
let transactionMessage =
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
);
|
|
414
|
+
let transactionMessage = createTransactionMessage({
|
|
415
|
+
version: 0
|
|
416
|
+
});
|
|
419
417
|
if (params?.computeUnitLimit !== void 0 && params?.computeUnitLimit > 0) {
|
|
420
418
|
const unitLimitInstruction = getSetComputeUnitLimitInstruction({
|
|
421
419
|
/** Transaction compute unit limit used for prioritization fees. */
|
|
@@ -467,11 +465,21 @@ var HyspSolana = class extends Blockchain {
|
|
|
467
465
|
fetchedTables
|
|
468
466
|
);
|
|
469
467
|
}
|
|
468
|
+
const txWithFeePayer = setTransactionMessageFeePayer(
|
|
469
|
+
address2(sender),
|
|
470
|
+
transactionMessage
|
|
471
|
+
);
|
|
470
472
|
const finalLatestBlockhash = params?.finalLatestBlockhash || (await this.connection.getLatestBlockhash().send()).value;
|
|
471
473
|
const txMessageWithBlockhashLifetime = setTransactionMessageLifetimeUsingBlockhash(
|
|
472
474
|
finalLatestBlockhash,
|
|
473
|
-
|
|
475
|
+
txWithFeePayer
|
|
474
476
|
);
|
|
477
|
+
const compiledTx = compileTransaction(txMessageWithBlockhashLifetime);
|
|
478
|
+
const serializedTx = getTransactionEncoder().encode(compiledTx);
|
|
479
|
+
const txSize = serializedTx.length;
|
|
480
|
+
if (txSize > MAX_TRANSACTION_SIZE) {
|
|
481
|
+
throw this.throwError("TX_TOO_LARGE");
|
|
482
|
+
}
|
|
475
483
|
return txMessageWithBlockhashLifetime;
|
|
476
484
|
}
|
|
477
485
|
};
|
|
@@ -479,6 +487,7 @@ export {
|
|
|
479
487
|
Blockchain,
|
|
480
488
|
ERROR_MESSAGES,
|
|
481
489
|
HyspSolana,
|
|
490
|
+
MAX_TRANSACTION_SIZE,
|
|
482
491
|
VAULTS,
|
|
483
492
|
WalletSDKError
|
|
484
493
|
};
|
package/jest.config.ts
CHANGED
|
@@ -9,10 +9,11 @@ const config: Config = {
|
|
|
9
9
|
collectCoverageFrom: [
|
|
10
10
|
'src/**/*.ts',
|
|
11
11
|
'!src/**/*.d.ts',
|
|
12
|
-
'!src
|
|
12
|
+
'!src/index.ts',
|
|
13
13
|
],
|
|
14
14
|
coverageDirectory: 'coverage',
|
|
15
15
|
coverageReporters: ['text', 'lcov', 'html'],
|
|
16
|
+
testTimeout: 60000,
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
export default config;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everstake/wallet-sdk-hysp-solana",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "HYSP Solana - Everstake Wallet SDK",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"sideEffects": false,
|
|
18
18
|
"scripts": {
|
|
19
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
19
|
+
"build": "pnpm run prebuild && tsup src/index.ts --format cjs,esm --dts",
|
|
20
20
|
"type-check": "tsc",
|
|
21
21
|
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
22
22
|
"prettier": "prettier --write 'src/**/*.{ts,tsx}'",
|
|
@@ -73,7 +73,17 @@
|
|
|
73
73
|
"js-yaml@>=4.0.0 <4.1.1": ">=4.1.1",
|
|
74
74
|
"glob@>=10.2.0 <10.5.0": ">=10.5.0",
|
|
75
75
|
"js-yaml": ">=4.1.1",
|
|
76
|
-
"glob": ">=10.5.0"
|
|
76
|
+
"glob": ">=10.5.0",
|
|
77
|
+
"lodash@>=4.0.0 <=4.17.22": ">=4.17.23",
|
|
78
|
+
"diff@>=4.0.0 <4.0.4": ">=4.0.4",
|
|
79
|
+
"@isaacs/brace-expansion@<=5.0.0": ">=5.0.1",
|
|
80
|
+
"axios@>=1.0.0 <=1.13.4": ">=1.13.5",
|
|
81
|
+
"bn.js@>=5.0.0 <5.2.3": ">=5.2.3",
|
|
82
|
+
"rollup@>=4.0.0 <4.59.0": ">=4.59.0",
|
|
83
|
+
"minimatch@<10.2.3": ">=10.2.3",
|
|
84
|
+
"flatted@<3.4.0": ">=3.4.0",
|
|
85
|
+
"flatted@<=3.4.1": ">=3.4.2",
|
|
86
|
+
"picomatch@<4.0.4": ">=4.0.4"
|
|
77
87
|
}
|
|
78
88
|
}
|
|
79
89
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025, Everstake.
|
|
3
|
+
* Licensed under the BSD-3-Clause License. See LICENSE file for details.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { HyspSolana } from '..';
|
|
7
|
+
import { SupportedToken } from '../constants';
|
|
8
|
+
import { WalletSDKError } from '../../../utils';
|
|
9
|
+
|
|
10
|
+
// Create a test class to access protected methods
|
|
11
|
+
class TestableHyspSolana extends HyspSolana {
|
|
12
|
+
public testProcessMemo(memo?: string) {
|
|
13
|
+
return this.processMemo(memo);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('HyspSolana processMemo method', () => {
|
|
18
|
+
const hyspSolana: TestableHyspSolana = new TestableHyspSolana(
|
|
19
|
+
'USDC' as SupportedToken,
|
|
20
|
+
'https://api.mainnet-beta.solana.com',
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
describe('Error cases', () => {
|
|
24
|
+
it('should throw MEMO_REQUIRED_ERROR when memo is undefined', () => {
|
|
25
|
+
expect(() => hyspSolana.testProcessMemo(undefined)).toThrow(
|
|
26
|
+
WalletSDKError,
|
|
27
|
+
);
|
|
28
|
+
expect(() => hyspSolana.testProcessMemo(undefined)).toThrow(
|
|
29
|
+
'Memo is required. Please contact us to get your referrer ID.',
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should throw MEMO_REQUIRED_ERROR when memo is empty string', () => {
|
|
34
|
+
expect(() => hyspSolana.testProcessMemo('')).toThrow(WalletSDKError);
|
|
35
|
+
expect(() => hyspSolana.testProcessMemo('')).toThrow(
|
|
36
|
+
'Memo is required. Please contact us to get your referrer ID.',
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should throw MEMO_REQUIRED_ERROR when memo is only whitespace', () => {
|
|
41
|
+
expect(() => hyspSolana.testProcessMemo(' ')).toThrow(WalletSDKError);
|
|
42
|
+
expect(() => hyspSolana.testProcessMemo(' ')).toThrow(
|
|
43
|
+
'Memo is required. Please contact us to get your referrer ID.',
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should throw MEMO_TOO_LONG_ERROR when processed memo exceeds 64 characters', () => {
|
|
48
|
+
const longMemo = 'a'.repeat(70);
|
|
49
|
+
expect(() => hyspSolana.testProcessMemo(longMemo)).toThrow(
|
|
50
|
+
WalletSDKError,
|
|
51
|
+
);
|
|
52
|
+
expect(() => hyspSolana.testProcessMemo(longMemo)).toThrow(
|
|
53
|
+
'Must be max 64 characters',
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should throw MEMO_INVALID_CHARACTERS_ERROR when memo contains invalid characters', () => {
|
|
58
|
+
expect(() => hyspSolana.testProcessMemo('test@domain')).toThrow(
|
|
59
|
+
WalletSDKError,
|
|
60
|
+
);
|
|
61
|
+
expect(() => hyspSolana.testProcessMemo('test@domain')).toThrow(
|
|
62
|
+
'Must contain only [A-Za-z0-9:_-] characters',
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should throw MEMO_INVALID_CHARACTERS_ERROR when memo contains spaces', () => {
|
|
67
|
+
expect(() => hyspSolana.testProcessMemo('test memo')).toThrow(
|
|
68
|
+
WalletSDKError,
|
|
69
|
+
);
|
|
70
|
+
expect(() => hyspSolana.testProcessMemo('test memo')).toThrow(
|
|
71
|
+
'Must contain only [A-Za-z0-9:_-] characters',
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should throw MEMO_INVALID_CHARACTERS_ERROR when memo contains special characters', () => {
|
|
76
|
+
expect(() => hyspSolana.testProcessMemo('test!memo#')).toThrow(
|
|
77
|
+
WalletSDKError,
|
|
78
|
+
);
|
|
79
|
+
expect(() => hyspSolana.testProcessMemo('test!memo#')).toThrow(
|
|
80
|
+
'Must contain only [A-Za-z0-9:_-] characters',
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Success cases', () => {
|
|
86
|
+
it('should prepend SDK: to memo without SDK prefix', () => {
|
|
87
|
+
const result = hyspSolana.testProcessMemo('acme:pilotQ1:prod:v1');
|
|
88
|
+
|
|
89
|
+
expect(new TextDecoder().decode(result.data)).toBe(
|
|
90
|
+
'SDK:acme:pilotQ1:prod:v1',
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should keep memo unchanged when it starts with SDK (but not SDK:)', () => {
|
|
95
|
+
const result = hyspSolana.testProcessMemo('SDKtest');
|
|
96
|
+
|
|
97
|
+
expect(new TextDecoder().decode(result.data)).toBe('SDKtest');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should keep memo unchanged when it starts with SDK:', () => {
|
|
101
|
+
const result = hyspSolana.testProcessMemo('SDK:bankxyz::prod:v1');
|
|
102
|
+
|
|
103
|
+
expect(new TextDecoder().decode(result.data)).toBe(
|
|
104
|
+
'SDK:bankxyz::prod:v1',
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should handle simple referrer ID', () => {
|
|
109
|
+
const result = hyspSolana.testProcessMemo('referrer123');
|
|
110
|
+
|
|
111
|
+
expect(new TextDecoder().decode(result.data)).toBe('SDK:referrer123');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle referrer ID with underscores and hyphens', () => {
|
|
115
|
+
const result = hyspSolana.testProcessMemo('test_ref-id_123');
|
|
116
|
+
|
|
117
|
+
expect(new TextDecoder().decode(result.data)).toBe('SDK:test_ref-id_123');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle referrer ID with colons', () => {
|
|
121
|
+
const result = hyspSolana.testProcessMemo('org:team:user');
|
|
122
|
+
|
|
123
|
+
expect(new TextDecoder().decode(result.data)).toBe('SDK:org:team:user');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle maximum allowed length without SDK prefix', () => {
|
|
127
|
+
const maxMemo = 'a'.repeat(59); // 59 + 'SDK:' = 63 chars (under limit)
|
|
128
|
+
const result = hyspSolana.testProcessMemo(maxMemo);
|
|
129
|
+
|
|
130
|
+
expect(new TextDecoder().decode(result.data)).toBe(`SDK:${maxMemo}`);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should handle maximum allowed length with SDK prefix', () => {
|
|
134
|
+
const maxMemo = 'SDK:' + 'a'.repeat(60); // exactly 64 chars
|
|
135
|
+
const result = hyspSolana.testProcessMemo(maxMemo);
|
|
136
|
+
|
|
137
|
+
expect(new TextDecoder().decode(result.data)).toBe(maxMemo);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should trim whitespace from memo', () => {
|
|
141
|
+
const result = hyspSolana.testProcessMemo(' referrer123 ');
|
|
142
|
+
|
|
143
|
+
expect(new TextDecoder().decode(result.data)).toBe('SDK:referrer123');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025, Everstake.
|
|
3
|
+
* Licensed under the BSD-3-Clause License. See LICENSE file for details.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { address, Instruction } from '@solana/kit';
|
|
7
|
+
import { Decimal } from 'decimal.js';
|
|
8
|
+
import { HyspSolana } from '../hysp';
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
interface BigInt {
|
|
12
|
+
toJSON(): number;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
BigInt.prototype.toJSON = function () {
|
|
17
|
+
return Number(this);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const PLACEHOLDER_USER_ADDRESS = address(
|
|
21
|
+
'Crc3qfV8QqdmGXNWSQnc8REsrDjViiSZMyjn6jsE51kj',
|
|
22
|
+
);
|
|
23
|
+
const PLACEHOLDER_TARGET_ADDRESS = address('11111111111111111111111111111113');
|
|
24
|
+
const PLACEHOLDER_SHARES_AMOUNT = new Decimal('0.1');
|
|
25
|
+
|
|
26
|
+
function createAdditionalInstruction(): Instruction {
|
|
27
|
+
return {
|
|
28
|
+
programAddress: address('11111111111111111111111111111112'),
|
|
29
|
+
accounts: [
|
|
30
|
+
{ address: PLACEHOLDER_USER_ADDRESS, role: 0 },
|
|
31
|
+
{ address: PLACEHOLDER_TARGET_ADDRESS, role: 0 },
|
|
32
|
+
],
|
|
33
|
+
data: new Uint8Array(
|
|
34
|
+
Array.from({ length: 12 }, () => Math.floor(Math.random() * 256)),
|
|
35
|
+
),
|
|
36
|
+
} as unknown as Instruction;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('HyspSolana Transaction Size Tests', () => {
|
|
40
|
+
let hyspSolana: HyspSolana;
|
|
41
|
+
|
|
42
|
+
beforeAll(() => {
|
|
43
|
+
hyspSolana = new HyspSolana('USDC');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('Transaction Size Limits', () => {
|
|
47
|
+
it('should fetch user shares successfully', async () => {
|
|
48
|
+
const userShares = await hyspSolana.getUserShares(
|
|
49
|
+
PLACEHOLDER_USER_ADDRESS,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
console.log(`User shares: ${userShares.result.toString()}`);
|
|
53
|
+
|
|
54
|
+
expect(userShares.result).toBeDefined();
|
|
55
|
+
expect(userShares.result).toBeInstanceOf(Decimal);
|
|
56
|
+
|
|
57
|
+
if (userShares.result.lt(PLACEHOLDER_SHARES_AMOUNT)) {
|
|
58
|
+
console.warn(
|
|
59
|
+
'Note: User has insufficient shares for withdrawal tests.',
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}, 30000);
|
|
63
|
+
|
|
64
|
+
it('should create a normal withdraw transaction within size limits', async () => {
|
|
65
|
+
const result = await hyspSolana.withdraw(
|
|
66
|
+
PLACEHOLDER_USER_ADDRESS,
|
|
67
|
+
PLACEHOLDER_SHARES_AMOUNT,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(result.result).toBeDefined();
|
|
71
|
+
}, 30000);
|
|
72
|
+
|
|
73
|
+
it('should fail when transaction exceeds size limits with too many instructions', async () => {
|
|
74
|
+
const additionalInstructions = [];
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < 100; i++) {
|
|
77
|
+
additionalInstructions.push(createAdditionalInstruction());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await expect(
|
|
81
|
+
hyspSolana.withdraw(
|
|
82
|
+
PLACEHOLDER_USER_ADDRESS,
|
|
83
|
+
PLACEHOLDER_SHARES_AMOUNT,
|
|
84
|
+
{
|
|
85
|
+
afterInstructions: additionalInstructions,
|
|
86
|
+
},
|
|
87
|
+
),
|
|
88
|
+
).rejects.toThrow(
|
|
89
|
+
'Transaction exceeds the maximum size limit of 1232 bytes',
|
|
90
|
+
);
|
|
91
|
+
}, 30000);
|
|
92
|
+
|
|
93
|
+
it('should handle edge case with some additional instructions', async () => {
|
|
94
|
+
const moderateInstructions = [];
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < 10; i++) {
|
|
97
|
+
moderateInstructions.push(createAdditionalInstruction());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const result = await hyspSolana.withdraw(
|
|
102
|
+
PLACEHOLDER_USER_ADDRESS,
|
|
103
|
+
PLACEHOLDER_SHARES_AMOUNT,
|
|
104
|
+
{
|
|
105
|
+
afterInstructions: moderateInstructions,
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(result.result).toBeDefined();
|
|
110
|
+
} catch (error: unknown) {
|
|
111
|
+
const errorMessage = (error as Error).message;
|
|
112
|
+
expect(errorMessage).toContain(
|
|
113
|
+
'Transaction exceeds the maximum size limit',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}, 30000);
|
|
117
|
+
});
|
|
118
|
+
});
|
package/src/constants/errors.ts
CHANGED
|
@@ -11,4 +11,10 @@ export const ERROR_MESSAGES = {
|
|
|
11
11
|
GET_BALANCE_ERROR: 'An error occurred while fetching user token balance',
|
|
12
12
|
VAULT_LOAD_ERROR: 'An error occurred while loading vault info',
|
|
13
13
|
VAULT_NOT_FOUND_ERROR: 'Vault not found for token: {0}',
|
|
14
|
+
TX_TOO_LARGE: 'Transaction exceeds the maximum size limit of 1232 bytes',
|
|
15
|
+
MEMO_REQUIRED_ERROR:
|
|
16
|
+
'Memo is required. Please contact us to get your referrer ID.',
|
|
17
|
+
MEMO_TOO_LONG_ERROR: 'Invalid memo: "{0}". Must be max 64 characters',
|
|
18
|
+
MEMO_INVALID_CHARACTERS_ERROR:
|
|
19
|
+
'Invalid memo: "{0}". Must contain only [A-Za-z0-9:_-] characters',
|
|
14
20
|
};
|
package/src/constants/index.ts
CHANGED
package/src/hysp.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
Address,
|
|
9
9
|
createNoopSigner,
|
|
10
10
|
address,
|
|
11
|
-
pipe,
|
|
12
11
|
createTransactionMessage,
|
|
13
12
|
setTransactionMessageFeePayer,
|
|
14
13
|
setTransactionMessageLifetimeUsingBlockhash,
|
|
@@ -21,6 +20,8 @@ import {
|
|
|
21
20
|
Rpc,
|
|
22
21
|
SolanaRpcApi,
|
|
23
22
|
Instruction,
|
|
23
|
+
compileTransaction,
|
|
24
|
+
getTransactionEncoder,
|
|
24
25
|
} from '@solana/kit';
|
|
25
26
|
|
|
26
27
|
import {
|
|
@@ -35,6 +36,7 @@ import { KaminoVault, VaultHoldings, APY } from '@kamino-finance/klend-sdk';
|
|
|
35
36
|
import { Decimal } from 'decimal.js';
|
|
36
37
|
import { Blockchain } from '../../utils';
|
|
37
38
|
import { ERROR_MESSAGES } from './constants/errors';
|
|
39
|
+
import { MAX_TRANSACTION_SIZE } from './constants';
|
|
38
40
|
import { VAULTS, SupportedToken, VaultInfo } from './constants';
|
|
39
41
|
import { ApiResponse, Params, VaultMeta } from './types';
|
|
40
42
|
|
|
@@ -349,33 +351,29 @@ export class HyspSolana extends Blockchain {
|
|
|
349
351
|
* @returns Memo instruction
|
|
350
352
|
*/
|
|
351
353
|
protected processMemo(memo?: string): Instruction {
|
|
352
|
-
//
|
|
353
|
-
let processedMemo: string;
|
|
354
|
+
// Check if memo is empty
|
|
354
355
|
if (!memo || memo.trim() === '') {
|
|
355
|
-
|
|
356
|
+
throw this.throwError('MEMO_REQUIRED_ERROR');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const trimmedMemo = memo.trim();
|
|
360
|
+
let processedMemo: string;
|
|
361
|
+
|
|
362
|
+
// If memo doesn't start with 'SDK', prepend 'SDK:'
|
|
363
|
+
if (!trimmedMemo.startsWith('SDK')) {
|
|
364
|
+
processedMemo = `SDK:${trimmedMemo}`;
|
|
356
365
|
} else {
|
|
357
|
-
|
|
358
|
-
if (trimmedMemo === 'SDK') {
|
|
359
|
-
processedMemo = trimmedMemo;
|
|
360
|
-
} else if (!trimmedMemo.startsWith('SDK:')) {
|
|
361
|
-
processedMemo = `SDK:${trimmedMemo}`;
|
|
362
|
-
} else {
|
|
363
|
-
processedMemo = trimmedMemo;
|
|
364
|
-
}
|
|
366
|
+
processedMemo = trimmedMemo;
|
|
365
367
|
}
|
|
366
368
|
|
|
367
369
|
// Validate memo
|
|
368
370
|
if (processedMemo.length > 64) {
|
|
369
|
-
throw
|
|
370
|
-
`Invalid memo: "${processedMemo}". Must be max 64 characters`,
|
|
371
|
-
);
|
|
371
|
+
throw this.throwError('MEMO_TOO_LONG_ERROR', processedMemo);
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
const validPattern = /^[A-Za-z0-9:_-]*$/;
|
|
375
375
|
if (!validPattern.test(processedMemo)) {
|
|
376
|
-
throw
|
|
377
|
-
`Invalid memo: "${processedMemo}". Must contain only [A-Za-z0-9:_-] characters`,
|
|
378
|
-
);
|
|
376
|
+
throw this.throwError('MEMO_INVALID_CHARACTERS_ERROR', processedMemo);
|
|
379
377
|
}
|
|
380
378
|
|
|
381
379
|
return getAddMemoInstruction({ memo: processedMemo });
|
|
@@ -387,10 +385,9 @@ export class HyspSolana extends Blockchain {
|
|
|
387
385
|
params?: Params,
|
|
388
386
|
lookupTableAddresses?: Address[],
|
|
389
387
|
): Promise<TransactionMessageWithLifetime> {
|
|
390
|
-
let transactionMessage: TransactionMessage =
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
);
|
|
388
|
+
let transactionMessage: TransactionMessage = createTransactionMessage({
|
|
389
|
+
version: 0,
|
|
390
|
+
});
|
|
394
391
|
|
|
395
392
|
if (
|
|
396
393
|
params?.computeUnitLimit !== undefined &&
|
|
@@ -457,6 +454,11 @@ export class HyspSolana extends Blockchain {
|
|
|
457
454
|
);
|
|
458
455
|
}
|
|
459
456
|
|
|
457
|
+
const txWithFeePayer = setTransactionMessageFeePayer(
|
|
458
|
+
address(sender),
|
|
459
|
+
transactionMessage,
|
|
460
|
+
);
|
|
461
|
+
|
|
460
462
|
const finalLatestBlockhash =
|
|
461
463
|
params?.finalLatestBlockhash ||
|
|
462
464
|
(await this.connection.getLatestBlockhash().send()).value;
|
|
@@ -464,9 +466,17 @@ export class HyspSolana extends Blockchain {
|
|
|
464
466
|
const txMessageWithBlockhashLifetime =
|
|
465
467
|
setTransactionMessageLifetimeUsingBlockhash(
|
|
466
468
|
finalLatestBlockhash,
|
|
467
|
-
|
|
469
|
+
txWithFeePayer,
|
|
468
470
|
);
|
|
469
471
|
|
|
472
|
+
const compiledTx = compileTransaction(txMessageWithBlockhashLifetime);
|
|
473
|
+
const serializedTx = getTransactionEncoder().encode(compiledTx);
|
|
474
|
+
const txSize = serializedTx.length;
|
|
475
|
+
|
|
476
|
+
if (txSize > MAX_TRANSACTION_SIZE) {
|
|
477
|
+
throw this.throwError('TX_TOO_LARGE');
|
|
478
|
+
}
|
|
479
|
+
|
|
470
480
|
return txMessageWithBlockhashLifetime;
|
|
471
481
|
}
|
|
472
482
|
}
|
package/src/__tests__/index.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2025, Everstake.
|
|
3
|
-
* Licensed under the BSD-3-Clause License. See LICENSE file for details.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { HyspSolana } from '..';
|
|
7
|
-
import { SupportedToken } from '../constants';
|
|
8
|
-
|
|
9
|
-
// Create a test class to access protected methods
|
|
10
|
-
class TestableHyspSolana extends HyspSolana {
|
|
11
|
-
public testProcessMemo(memo?: string) {
|
|
12
|
-
return this.processMemo(memo);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('HyspSolana processMemo method', () => {
|
|
17
|
-
const hyspSolana: TestableHyspSolana = new TestableHyspSolana(
|
|
18
|
-
'USDC' as SupportedToken,
|
|
19
|
-
'https://api.mainnet-beta.solana.com',
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
it('should return SDK when memo is empty', () => {
|
|
23
|
-
const result = hyspSolana.testProcessMemo('');
|
|
24
|
-
|
|
25
|
-
expect(new TextDecoder().decode(result.data)).toBe('SDK');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should prepend SDK: to memo without SDK prefix', () => {
|
|
29
|
-
const result = hyspSolana.testProcessMemo('acme:pilotQ1:prod:v1');
|
|
30
|
-
|
|
31
|
-
expect(new TextDecoder().decode(result.data)).toBe(
|
|
32
|
-
'SDK:acme:pilotQ1:prod:v1',
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should keep memo unchanged when it has SDK prefix', () => {
|
|
37
|
-
const result = hyspSolana.testProcessMemo('SDK:bankxyz::prod:v1');
|
|
38
|
-
|
|
39
|
-
expect(new TextDecoder().decode(result.data)).toBe('SDK:bankxyz::prod:v1');
|
|
40
|
-
});
|
|
41
|
-
});
|