sol-trade-sdk 0.1.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.
Files changed (87) hide show
  1. package/README.md +390 -0
  2. package/dist/chunk-MMQAMIKR.mjs +3735 -0
  3. package/dist/chunk-NEZDFAYA.mjs +7744 -0
  4. package/dist/clients-VITWK7B6.mjs +1370 -0
  5. package/dist/index-1BK_FXsW.d.mts +2327 -0
  6. package/dist/index-1BK_FXsW.d.ts +2327 -0
  7. package/dist/index.d.mts +2659 -0
  8. package/dist/index.d.ts +2659 -0
  9. package/dist/index.js +13265 -0
  10. package/dist/index.mjs +562 -0
  11. package/dist/perf/index.d.mts +2 -0
  12. package/dist/perf/index.d.ts +2 -0
  13. package/dist/perf/index.js +3742 -0
  14. package/dist/perf/index.mjs +214 -0
  15. package/package.json +101 -0
  16. package/src/__tests__/complete_sdk.test.ts +354 -0
  17. package/src/__tests__/hotpath.test.ts +486 -0
  18. package/src/__tests__/nonce.test.ts +45 -0
  19. package/src/__tests__/sdk.test.ts +425 -0
  20. package/src/address-lookup/index.ts +197 -0
  21. package/src/cache/cache.ts +308 -0
  22. package/src/calc/index.ts +1058 -0
  23. package/src/calc/pumpfun.ts +124 -0
  24. package/src/common/bonding_curve.ts +272 -0
  25. package/src/common/compute-budget.ts +148 -0
  26. package/src/common/confirm-any-signature.ts +184 -0
  27. package/src/common/fast-timing.ts +481 -0
  28. package/src/common/fast_fn.ts +150 -0
  29. package/src/common/gas-fee-strategy.ts +253 -0
  30. package/src/common/map-pool.ts +23 -0
  31. package/src/common/nonce.ts +40 -0
  32. package/src/common/sdk-log.ts +460 -0
  33. package/src/common/seed.ts +381 -0
  34. package/src/common/spl-token.ts +578 -0
  35. package/src/common/subscription-handle.ts +644 -0
  36. package/src/common/trading-utils.ts +239 -0
  37. package/src/common/wsol-manager.ts +325 -0
  38. package/src/compute/compute_budget_manager.ts +187 -0
  39. package/src/compute/index.ts +21 -0
  40. package/src/constants/index.ts +96 -0
  41. package/src/execution/execution.ts +532 -0
  42. package/src/execution/index.ts +42 -0
  43. package/src/hotpath/executor.ts +464 -0
  44. package/src/hotpath/index.ts +64 -0
  45. package/src/hotpath/state.ts +435 -0
  46. package/src/index.ts +2117 -0
  47. package/src/instruction/bonk_builder.ts +730 -0
  48. package/src/instruction/index.ts +24 -0
  49. package/src/instruction/meteora_damm_v2_builder.ts +509 -0
  50. package/src/instruction/pumpfun_builder.ts +1183 -0
  51. package/src/instruction/pumpswap.ts +1123 -0
  52. package/src/instruction/raydium_amm_v4_builder.ts +692 -0
  53. package/src/instruction/raydium_cpmm_builder.ts +795 -0
  54. package/src/middleware/traits.ts +407 -0
  55. package/src/params/index.ts +483 -0
  56. package/src/perf/compiler-optimization.ts +529 -0
  57. package/src/perf/hardware.ts +631 -0
  58. package/src/perf/index.ts +9 -0
  59. package/src/perf/kernel-bypass.ts +656 -0
  60. package/src/perf/protocol.ts +682 -0
  61. package/src/perf/realtime.ts +592 -0
  62. package/src/perf/simd.ts +668 -0
  63. package/src/perf/syscall-bypass.ts +331 -0
  64. package/src/perf/ultra-low-latency.ts +505 -0
  65. package/src/perf/zero-copy.ts +589 -0
  66. package/src/pool/pool.ts +294 -0
  67. package/src/rpc/client.ts +345 -0
  68. package/src/sdk-errors.ts +13 -0
  69. package/src/security/index.ts +26 -0
  70. package/src/security/secure-key.ts +303 -0
  71. package/src/security/validators.ts +281 -0
  72. package/src/seed/pda.ts +262 -0
  73. package/src/serialization/index.ts +28 -0
  74. package/src/serialization/serialization.ts +288 -0
  75. package/src/swqos/clients.ts +1754 -0
  76. package/src/swqos/index.ts +50 -0
  77. package/src/swqos/providers.ts +1707 -0
  78. package/src/trading/core/async-executor.ts +702 -0
  79. package/src/trading/core/confirmation-monitor.ts +711 -0
  80. package/src/trading/core/index.ts +82 -0
  81. package/src/trading/core/retry-handler.ts +683 -0
  82. package/src/trading/core/transaction-pool.ts +780 -0
  83. package/src/trading/executor.ts +385 -0
  84. package/src/trading/factory.ts +282 -0
  85. package/src/trading/index.ts +30 -0
  86. package/src/types.ts +8 -0
  87. package/src/utils/index.ts +155 -0
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Secure key storage and management for Sol Trade SDK
3
+ *
4
+ * Implements secure memory handling for private keys with:
5
+ * - Memory encryption at rest
6
+ * - Secure zeroing after use
7
+ * - Context manager for automatic cleanup
8
+ */
9
+
10
+ import { Keypair, PublicKey } from '@solana/web3.js';
11
+ import * as crypto from 'crypto';
12
+ import * as nacl from 'tweetnacl';
13
+
14
+ /**
15
+ * Error thrown when secure key operation fails
16
+ */
17
+ export class SecureKeyError extends Error {
18
+ constructor(message: string) {
19
+ super(message);
20
+ this.name = 'SecureKeyError';
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Error thrown when trying to access a cleared key
26
+ */
27
+ export class KeyNotAvailableError extends SecureKeyError {
28
+ constructor(message: string = 'Key not available') {
29
+ super(message);
30
+ this.name = 'KeyNotAvailableError';
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Metadata about a stored key
36
+ */
37
+ export interface KeyMetadata {
38
+ pubkey: string;
39
+ createdAt: number;
40
+ lastAccessed?: number;
41
+ accessCount: number;
42
+ }
43
+
44
+ /**
45
+ * Secure storage for Solana private keys.
46
+ *
47
+ * Features:
48
+ * - Keys are encrypted in memory when not in use
49
+ * - Automatic secure zeroing of key material
50
+ * - Context manager support for temporary key access
51
+ */
52
+ export class SecureKeyStorage {
53
+ private encryptedKey: Buffer | null = null;
54
+ private salt: Buffer | null = null;
55
+ private pubkey: string | null = null;
56
+ private isUnlocked: boolean = false;
57
+ private unlockedKey: Buffer | null = null;
58
+ private metadata: KeyMetadata | null = null;
59
+ private passwordProtected: boolean = false;
60
+
61
+ private constructor() {}
62
+
63
+ /**
64
+ * Create secure storage from a Keypair.
65
+ */
66
+ static fromKeypair(keypair: Keypair, password?: string): SecureKeyStorage {
67
+ const storage = new SecureKeyStorage();
68
+ storage.pubkey = keypair.publicKey.toBase58();
69
+
70
+ // Get secret key bytes
71
+ const secretBytes = Buffer.from(keypair.secretKey);
72
+
73
+ try {
74
+ if (password) {
75
+ storage.passwordProtected = true;
76
+ storage.salt = crypto.randomBytes(16);
77
+ storage.encryptedKey = storage.encryptWithPassword(
78
+ secretBytes,
79
+ password,
80
+ storage.salt
81
+ );
82
+ } else {
83
+ // Simple XOR encryption with random key (better than plaintext)
84
+ storage.salt = crypto.randomBytes(64);
85
+ storage.encryptedKey = storage.xorEncrypt(secretBytes, storage.salt);
86
+ }
87
+
88
+ storage.metadata = {
89
+ pubkey: storage.pubkey,
90
+ createdAt: Date.now(),
91
+ accessCount: 0,
92
+ };
93
+ } finally {
94
+ // Always clear the secret bytes from memory
95
+ storage.secureZero(secretBytes);
96
+ }
97
+
98
+ return storage;
99
+ }
100
+
101
+ /**
102
+ * Create secure storage from a seed.
103
+ */
104
+ static fromSeed(seed: Uint8Array, password?: string): SecureKeyStorage {
105
+ if (seed.length !== 32) {
106
+ throw new SecureKeyError(`Seed must be 32 bytes, got ${seed.length}`);
107
+ }
108
+
109
+ const keypair = Keypair.fromSeed(seed);
110
+ try {
111
+ return SecureKeyStorage.fromKeypair(keypair, password);
112
+ } finally {
113
+ // Clear seed from memory
114
+ SecureKeyStorage.secureZero(Buffer.from(seed));
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Create secure storage from a mnemonic phrase.
120
+ */
121
+ static fromMnemonic(mnemonic: string, password?: string): SecureKeyStorage {
122
+ // Simplified implementation - in production use proper BIP39 derivation
123
+ const seed = crypto
124
+ .pbkdf2Sync(mnemonic, 'mnemonic', 2048, 32, 'sha512');
125
+ const keypair = Keypair.fromSeed(seed);
126
+ try {
127
+ return SecureKeyStorage.fromKeypair(keypair, password);
128
+ } finally {
129
+ SecureKeyStorage.secureZero(seed);
130
+ }
131
+ }
132
+
133
+ private encryptWithPassword(data: Buffer, password: string, salt: Buffer): Buffer {
134
+ const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
135
+ const iv = crypto.randomBytes(16);
136
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
137
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
138
+ const authTag = cipher.getAuthTag();
139
+ return Buffer.concat([iv, authTag, encrypted]);
140
+ }
141
+
142
+ private decryptWithPassword(encryptedData: Buffer, password: string, salt: Buffer): Buffer {
143
+ const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
144
+ const iv = encryptedData.slice(0, 16);
145
+ const authTag = encryptedData.slice(16, 32);
146
+ const encrypted = encryptedData.slice(32);
147
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
148
+ decipher.setAuthTag(authTag);
149
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]);
150
+ }
151
+
152
+ private xorEncrypt(data: Buffer, key: Buffer): Buffer {
153
+ const result = Buffer.alloc(data.length);
154
+ for (let i = 0; i < data.length; i++) {
155
+ const dataByte = data[i] ?? 0;
156
+ const keyByte = key[i % key.length] ?? 0;
157
+ result[i] = dataByte ^ keyByte;
158
+ }
159
+ return result;
160
+ }
161
+
162
+ /**
163
+ * Securely zero out sensitive data from memory
164
+ */
165
+ private static secureZero(data: Buffer): void {
166
+ // Overwrite with zeros
167
+ data.fill(0);
168
+ // Overwrite with ones
169
+ data.fill(0xff);
170
+ // Overwrite with random
171
+ crypto.randomFillSync(data);
172
+ // Final zero
173
+ data.fill(0);
174
+ }
175
+
176
+ private secureZero(data: Buffer): void {
177
+ SecureKeyStorage.secureZero(data);
178
+ }
179
+
180
+ /**
181
+ * Temporarily access the keypair.
182
+ * Key is automatically cleared after callback completes.
183
+ */
184
+ async withKeypair<T>(
185
+ callback: (keypair: Keypair) => Promise<T>,
186
+ password?: string
187
+ ): Promise<T> {
188
+ if (!this.encryptedKey) {
189
+ throw new KeyNotAvailableError('No key stored');
190
+ }
191
+
192
+ if (this.passwordProtected && !password) {
193
+ throw new SecureKeyError('Password required to unlock');
194
+ }
195
+
196
+ let decrypted: Buffer | null = null;
197
+
198
+ try {
199
+ // Decrypt the key
200
+ if (this.passwordProtected && this.salt) {
201
+ decrypted = this.decryptWithPassword(this.encryptedKey, password!, this.salt);
202
+ } else if (this.salt) {
203
+ decrypted = this.xorEncrypt(this.encryptedKey, this.salt);
204
+ } else {
205
+ throw new SecureKeyError('Invalid storage state');
206
+ }
207
+
208
+ // Create keypair from decrypted bytes
209
+ const keypair = Keypair.fromSeed(decrypted.slice(0, 32));
210
+
211
+ // Update metadata
212
+ if (this.metadata) {
213
+ this.metadata.lastAccessed = Date.now();
214
+ this.metadata.accessCount++;
215
+ }
216
+
217
+ this.isUnlocked = true;
218
+ this.unlockedKey = decrypted;
219
+
220
+ return await callback(keypair);
221
+ } finally {
222
+ // Always cleanup
223
+ this.isUnlocked = false;
224
+ if (this.unlockedKey) {
225
+ this.secureZero(this.unlockedKey);
226
+ this.unlockedKey = null;
227
+ }
228
+ if (decrypted) {
229
+ this.secureZero(decrypted);
230
+ }
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Sign a message without exposing the keypair.
236
+ */
237
+ async signMessage(message: Buffer | Uint8Array, password?: string): Promise<Buffer> {
238
+ return this.withKeypair(async (keypair) => {
239
+ const signature = nacl.sign.detached(Buffer.from(message), keypair.secretKey);
240
+ return Buffer.from(signature);
241
+ }, password);
242
+ }
243
+
244
+ /**
245
+ * Get the public key (safe to access)
246
+ */
247
+ getPublicKey(): string {
248
+ return this.pubkey || '';
249
+ }
250
+
251
+ /**
252
+ * Check if storage requires password
253
+ */
254
+ get isPasswordProtected(): boolean {
255
+ return this.passwordProtected;
256
+ }
257
+
258
+ /**
259
+ * Get key metadata
260
+ */
261
+ getMetadata(): KeyMetadata | null {
262
+ return this.metadata;
263
+ }
264
+
265
+ /**
266
+ * Permanently clear all key material
267
+ */
268
+ clear(): void {
269
+ if (this.encryptedKey) {
270
+ this.secureZero(this.encryptedKey);
271
+ this.encryptedKey = null;
272
+ }
273
+ if (this.salt) {
274
+ this.secureZero(this.salt);
275
+ this.salt = null;
276
+ }
277
+ if (this.unlockedKey) {
278
+ this.secureZero(this.unlockedKey);
279
+ this.unlockedKey = null;
280
+ }
281
+ this.pubkey = null;
282
+ this.metadata = null;
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Convenience function for quick signing
288
+ */
289
+ export async function signWithKeypair(
290
+ keypair: Keypair,
291
+ message: Buffer | Uint8Array,
292
+ clearAfter: boolean = false
293
+ ): Promise<Buffer> {
294
+ const signature = nacl.sign.detached(Buffer.from(message), keypair.secretKey);
295
+
296
+ if (clearAfter) {
297
+ // Attempt to clear sensitive data
298
+ const secret = Buffer.from(keypair.secretKey);
299
+ SecureKeyStorage['secureZero'](secret);
300
+ }
301
+
302
+ return Buffer.from(signature);
303
+ }
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Input validators for Sol Trade SDK
3
+ *
4
+ * Provides secure input validation for:
5
+ * - RPC URLs
6
+ * - Program IDs
7
+ * - Amounts
8
+ * - Slippage values
9
+ */
10
+
11
+ import { PublicKey } from '@solana/web3.js';
12
+
13
+ /**
14
+ * Error thrown when input validation fails
15
+ */
16
+ export class ValidationError extends Error {
17
+ constructor(message: string) {
18
+ super(message);
19
+ this.name = 'ValidationError';
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Known legitimate Solana program IDs
25
+ */
26
+ export const KNOWN_PROGRAM_IDS: Record<string, string[]> = {
27
+ // PumpFun
28
+ pumpfun: ['6EF8rrecthR5Dkzon8Nwu78hRvfCKopJFfWcCzNfXt3D'],
29
+ // PumpSwap
30
+ pumpswap: ['pAMMBay6oceH9fJKBRdGP4LmVn7LKwEqT7dPWn1oLKs'],
31
+ // Raydium
32
+ raydium: [
33
+ 'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK', // CPMM
34
+ '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8', // AMM V4
35
+ ],
36
+ // Meteora
37
+ meteora: ['MERLuDFBMmsHnsBPZw2sDQZHvXFM4sPkHePSuUZnPdK'], // DAMM V2
38
+ // Bonk
39
+ bonk: ['bLGPY3zYMBUfok1bMna4jrHGG3QdhSCuLZxUx2fMMLo'],
40
+ // System programs
41
+ system: [
42
+ '11111111111111111111111111111111', // System Program
43
+ 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', // Token Program
44
+ 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', // Token-2022
45
+ 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL', // Associated Token
46
+ ],
47
+ };
48
+
49
+ // Base58 character set
50
+ const BASE58_CHARS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
51
+ const BASE58_REGEX = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
52
+
53
+ // Private IP patterns
54
+ const PRIVATE_IP_PATTERNS = [
55
+ /^127\./,
56
+ /^10\./,
57
+ /^172\.(1[6-9]|2[0-9]|3[01])\./,
58
+ /^192\.168\./,
59
+ /^0\./,
60
+ /^localhost$/i,
61
+ ];
62
+
63
+ /**
64
+ * Validate RPC URL format and security.
65
+ */
66
+ export function validateRpcUrl(url: string, allowHttp: boolean = false): string {
67
+ if (!url) {
68
+ throw new ValidationError('RPC URL cannot be empty');
69
+ }
70
+
71
+ let parsed: URL;
72
+ try {
73
+ parsed = new URL(url);
74
+ } catch {
75
+ throw new ValidationError(`Invalid URL format: ${url}`);
76
+ }
77
+
78
+ // Check scheme
79
+ if (!['https:', 'http:'].includes(parsed.protocol)) {
80
+ throw new ValidationError(`Invalid URL scheme: ${parsed.protocol}. Must be http or https`);
81
+ }
82
+
83
+ if (parsed.protocol === 'http:' && !allowHttp) {
84
+ throw new ValidationError(
85
+ 'HTTP RPC URLs are insecure. Use HTTPS or set allowHttp=true if you understand the risks'
86
+ );
87
+ }
88
+
89
+ // Check hostname
90
+ const hostname = parsed.hostname.toLowerCase();
91
+
92
+ // Block localhost and private IPs in production
93
+ for (const pattern of PRIVATE_IP_PATTERNS) {
94
+ if (pattern.test(hostname)) {
95
+ throw new ValidationError(
96
+ `Private IP/localhost RPC URLs are not allowed for security: ${hostname}`
97
+ );
98
+ }
99
+ }
100
+
101
+ // Check port
102
+ if (parsed.port) {
103
+ const port = parseInt(parsed.port, 10);
104
+ if (port < 1 || port > 65535) {
105
+ throw new ValidationError(`Invalid port number: ${port}`);
106
+ }
107
+ }
108
+
109
+ // Reconstruct clean URL
110
+ let cleanUrl = `${parsed.protocol}//${parsed.hostname}`;
111
+ if (parsed.port) {
112
+ cleanUrl += `:${parsed.port}`;
113
+ }
114
+ if (parsed.pathname && parsed.pathname !== '/') {
115
+ cleanUrl += parsed.pathname;
116
+ }
117
+
118
+ return cleanUrl;
119
+ }
120
+
121
+ /**
122
+ * Validate a Solana program ID.
123
+ */
124
+ export function validateProgramId(programId: string, expectedProgram?: string): string {
125
+ if (!programId) {
126
+ throw new ValidationError('Program ID cannot be empty');
127
+ }
128
+
129
+ // Check base58 format
130
+ if (!BASE58_REGEX.test(programId)) {
131
+ throw new ValidationError(`Invalid base58 characters in program ID: ${programId}`);
132
+ }
133
+
134
+ // Check length
135
+ if (programId.length < 32 || programId.length > 48) {
136
+ throw new ValidationError(`Invalid program ID length: ${programId.length} (expected 32-48)`);
137
+ }
138
+
139
+ // Verify against known program IDs if expected
140
+ if (expectedProgram) {
141
+ const expectedIds = KNOWN_PROGRAM_IDS[expectedProgram.toLowerCase()];
142
+ if (expectedIds && !expectedIds.includes(programId)) {
143
+ throw new ValidationError(
144
+ `Program ID ${programId} does not match known ${expectedProgram} program IDs. ` +
145
+ `Expected one of: ${expectedIds.slice(0, 3).join(', ')}`
146
+ );
147
+ }
148
+ }
149
+
150
+ return programId;
151
+ }
152
+
153
+ /**
154
+ * Validate an amount value.
155
+ */
156
+ export function validateAmount(
157
+ amount: number | bigint,
158
+ name: string = 'amount',
159
+ allowZero: boolean = false
160
+ ): bigint {
161
+ const bigAmount = typeof amount === 'bigint' ? amount : BigInt(amount);
162
+
163
+ if (bigAmount < BigInt(0)) {
164
+ throw new ValidationError(`${name} cannot be negative: ${amount}`);
165
+ }
166
+
167
+ if (bigAmount === BigInt(0) && !allowZero) {
168
+ throw new ValidationError(`${name} cannot be zero`);
169
+ }
170
+
171
+ // Check for reasonable upper bound
172
+ const maxSafe = BigInt('9223372036854775807'); // Max i64
173
+ if (bigAmount > maxSafe) {
174
+ throw new ValidationError(`${name} exceeds maximum safe value: ${amount} > ${maxSafe}`);
175
+ }
176
+
177
+ return bigAmount;
178
+ }
179
+
180
+ /**
181
+ * Validate slippage in basis points.
182
+ */
183
+ export function validateSlippage(slippageBasisPoints: number): number {
184
+ if (!Number.isInteger(slippageBasisPoints)) {
185
+ throw new ValidationError(`Slippage must be an integer, got ${typeof slippageBasisPoints}`);
186
+ }
187
+
188
+ if (slippageBasisPoints < 0) {
189
+ throw new ValidationError(`Slippage cannot be negative: ${slippageBasisPoints}`);
190
+ }
191
+
192
+ if (slippageBasisPoints > 10000) {
193
+ throw new ValidationError(
194
+ `Slippage cannot exceed 10000 basis points (100%), got ${slippageBasisPoints}`
195
+ );
196
+ }
197
+
198
+ // Warn on high slippage
199
+ if (slippageBasisPoints > 1000) {
200
+ console.warn(
201
+ `High slippage detected: ${slippageBasisPoints} bp (${slippageBasisPoints / 100}%). ` +
202
+ 'This may result in significant price impact.'
203
+ );
204
+ }
205
+
206
+ return slippageBasisPoints;
207
+ }
208
+
209
+ /**
210
+ * Validate a Solana public key.
211
+ */
212
+ export function validatePubkey(pubkey: string | PublicKey, name: string = 'pubkey'): PublicKey {
213
+ if (!pubkey) {
214
+ throw new ValidationError(`${name} cannot be empty`);
215
+ }
216
+
217
+ const pubkeyStr = typeof pubkey === 'string' ? pubkey : pubkey.toBase58();
218
+
219
+ // Check base58 format
220
+ if (!BASE58_REGEX.test(pubkeyStr)) {
221
+ throw new ValidationError(`Invalid base58 characters in ${name}: ${pubkeyStr}`);
222
+ }
223
+
224
+ // Check length
225
+ if (pubkeyStr.length < 32 || pubkeyStr.length > 48) {
226
+ throw new ValidationError(`Invalid ${name} length: ${pubkeyStr.length} (expected 32-48)`);
227
+ }
228
+
229
+ try {
230
+ return new PublicKey(pubkeyStr);
231
+ } catch (error) {
232
+ throw new ValidationError(`Invalid ${name}: ${pubkeyStr} - ${error}`);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Validate a trading pair.
238
+ */
239
+ export function validateMintPair(inputMint: string, outputMint: string): void {
240
+ validatePubkey(inputMint, 'input_mint');
241
+ validatePubkey(outputMint, 'output_mint');
242
+
243
+ if (inputMint === outputMint) {
244
+ throw new ValidationError('Input and output mint cannot be the same');
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Validate transaction size.
250
+ */
251
+ export function validateTransactionSize(transactionBytes: Buffer | Uint8Array, maxSize: number = 1232): void {
252
+ if (!(transactionBytes instanceof Buffer) && !(transactionBytes instanceof Uint8Array)) {
253
+ throw new ValidationError(`Transaction must be bytes, got ${typeof transactionBytes}`);
254
+ }
255
+
256
+ if (transactionBytes.length > maxSize) {
257
+ throw new ValidationError(
258
+ `Transaction size ${transactionBytes.length} exceeds maximum ${maxSize} bytes`
259
+ );
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Validate a signature string.
265
+ */
266
+ export function validateSignature(signature: string): string {
267
+ if (!signature) {
268
+ throw new ValidationError('Signature cannot be empty');
269
+ }
270
+
271
+ // Signatures are 64 bytes = ~88 base58 chars
272
+ if (!BASE58_REGEX.test(signature)) {
273
+ throw new ValidationError(`Invalid base58 characters in signature: ${signature}`);
274
+ }
275
+
276
+ if (signature.length < 80 || signature.length > 96) {
277
+ throw new ValidationError(`Invalid signature length: ${signature.length}`);
278
+ }
279
+
280
+ return signature;
281
+ }