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.
- package/README.md +390 -0
- package/dist/chunk-MMQAMIKR.mjs +3735 -0
- package/dist/chunk-NEZDFAYA.mjs +7744 -0
- package/dist/clients-VITWK7B6.mjs +1370 -0
- package/dist/index-1BK_FXsW.d.mts +2327 -0
- package/dist/index-1BK_FXsW.d.ts +2327 -0
- package/dist/index.d.mts +2659 -0
- package/dist/index.d.ts +2659 -0
- package/dist/index.js +13265 -0
- package/dist/index.mjs +562 -0
- package/dist/perf/index.d.mts +2 -0
- package/dist/perf/index.d.ts +2 -0
- package/dist/perf/index.js +3742 -0
- package/dist/perf/index.mjs +214 -0
- package/package.json +101 -0
- package/src/__tests__/complete_sdk.test.ts +354 -0
- package/src/__tests__/hotpath.test.ts +486 -0
- package/src/__tests__/nonce.test.ts +45 -0
- package/src/__tests__/sdk.test.ts +425 -0
- package/src/address-lookup/index.ts +197 -0
- package/src/cache/cache.ts +308 -0
- package/src/calc/index.ts +1058 -0
- package/src/calc/pumpfun.ts +124 -0
- package/src/common/bonding_curve.ts +272 -0
- package/src/common/compute-budget.ts +148 -0
- package/src/common/confirm-any-signature.ts +184 -0
- package/src/common/fast-timing.ts +481 -0
- package/src/common/fast_fn.ts +150 -0
- package/src/common/gas-fee-strategy.ts +253 -0
- package/src/common/map-pool.ts +23 -0
- package/src/common/nonce.ts +40 -0
- package/src/common/sdk-log.ts +460 -0
- package/src/common/seed.ts +381 -0
- package/src/common/spl-token.ts +578 -0
- package/src/common/subscription-handle.ts +644 -0
- package/src/common/trading-utils.ts +239 -0
- package/src/common/wsol-manager.ts +325 -0
- package/src/compute/compute_budget_manager.ts +187 -0
- package/src/compute/index.ts +21 -0
- package/src/constants/index.ts +96 -0
- package/src/execution/execution.ts +532 -0
- package/src/execution/index.ts +42 -0
- package/src/hotpath/executor.ts +464 -0
- package/src/hotpath/index.ts +64 -0
- package/src/hotpath/state.ts +435 -0
- package/src/index.ts +2117 -0
- package/src/instruction/bonk_builder.ts +730 -0
- package/src/instruction/index.ts +24 -0
- package/src/instruction/meteora_damm_v2_builder.ts +509 -0
- package/src/instruction/pumpfun_builder.ts +1183 -0
- package/src/instruction/pumpswap.ts +1123 -0
- package/src/instruction/raydium_amm_v4_builder.ts +692 -0
- package/src/instruction/raydium_cpmm_builder.ts +795 -0
- package/src/middleware/traits.ts +407 -0
- package/src/params/index.ts +483 -0
- package/src/perf/compiler-optimization.ts +529 -0
- package/src/perf/hardware.ts +631 -0
- package/src/perf/index.ts +9 -0
- package/src/perf/kernel-bypass.ts +656 -0
- package/src/perf/protocol.ts +682 -0
- package/src/perf/realtime.ts +592 -0
- package/src/perf/simd.ts +668 -0
- package/src/perf/syscall-bypass.ts +331 -0
- package/src/perf/ultra-low-latency.ts +505 -0
- package/src/perf/zero-copy.ts +589 -0
- package/src/pool/pool.ts +294 -0
- package/src/rpc/client.ts +345 -0
- package/src/sdk-errors.ts +13 -0
- package/src/security/index.ts +26 -0
- package/src/security/secure-key.ts +303 -0
- package/src/security/validators.ts +281 -0
- package/src/seed/pda.ts +262 -0
- package/src/serialization/index.ts +28 -0
- package/src/serialization/serialization.ts +288 -0
- package/src/swqos/clients.ts +1754 -0
- package/src/swqos/index.ts +50 -0
- package/src/swqos/providers.ts +1707 -0
- package/src/trading/core/async-executor.ts +702 -0
- package/src/trading/core/confirmation-monitor.ts +711 -0
- package/src/trading/core/index.ts +82 -0
- package/src/trading/core/retry-handler.ts +683 -0
- package/src/trading/core/transaction-pool.ts +780 -0
- package/src/trading/executor.ts +385 -0
- package/src/trading/factory.ts +282 -0
- package/src/trading/index.ts +30 -0
- package/src/types.ts +8 -0
- package/src/utils/index.ts +155 -0
package/src/seed/pda.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed-based PDA Derivation for Sol Trade SDK
|
|
3
|
+
* High-performance PDA computation with caching.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { LRUCache } from '../cache/cache';
|
|
7
|
+
|
|
8
|
+
// ===== Constants =====
|
|
9
|
+
|
|
10
|
+
// Program IDs - MUST match src/constants/index.ts
|
|
11
|
+
// These are the official Solana mainnet program IDs
|
|
12
|
+
export const PUMPFUN_PROGRAM_ID = '6EF8rrecthR5Dkzon8Nwu78hRvfCKopJFfWcCzNfXt3D';
|
|
13
|
+
export const PUMPSWAP_PROGRAM_ID = 'pAMMBay6oceH9fJKBRdGP4LmVn7LKwEqT7dPWn1oLKs';
|
|
14
|
+
export const RAYDIUM_AMM_V4_PROGRAM_ID = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8';
|
|
15
|
+
export const RAYDIUM_CPMM_PROGRAM_ID = 'CPMMoo8L3F4NbTUBBfMTm5L2AhwDtLd6P4VeXvgQA2Po';
|
|
16
|
+
export const METEORA_DAMM_V2_PROGRAM_ID = 'LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YvKpNLuh';
|
|
17
|
+
export const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
|
|
18
|
+
export const TOKEN_2022_PROGRAM_ID = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
|
|
19
|
+
export const ASSOCIATED_TOKEN_PROGRAM_ID = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
|
|
20
|
+
|
|
21
|
+
// ===== Types =====
|
|
22
|
+
|
|
23
|
+
export interface PDA {
|
|
24
|
+
pubkey: Buffer;
|
|
25
|
+
bump: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ===== PDA Cache =====
|
|
29
|
+
|
|
30
|
+
const pdaCache = new LRUCache<string, PDA>(1000, 60000);
|
|
31
|
+
|
|
32
|
+
// ===== Helper Functions =====
|
|
33
|
+
|
|
34
|
+
function concatBuffers(...buffers: Buffer[]): Buffer {
|
|
35
|
+
const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);
|
|
36
|
+
const result = Buffer.alloc(totalLength);
|
|
37
|
+
let offset = 0;
|
|
38
|
+
for (const buf of buffers) {
|
|
39
|
+
buf.copy(result, offset);
|
|
40
|
+
offset += buf.length;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function sha256(data: Buffer): Promise<Buffer> {
|
|
46
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
47
|
+
return Buffer.from(hashBuffer);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ===== Base58 =====
|
|
51
|
+
|
|
52
|
+
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
53
|
+
|
|
54
|
+
export function base58Encode(buffer: Buffer): string {
|
|
55
|
+
const digits = [0];
|
|
56
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
57
|
+
let carry = buffer[i]!;
|
|
58
|
+
for (let j = 0; j < digits.length; j++) {
|
|
59
|
+
carry += digits[j]! << 8;
|
|
60
|
+
digits[j] = carry % 58;
|
|
61
|
+
carry = (carry / 58) | 0;
|
|
62
|
+
}
|
|
63
|
+
while (carry > 0) {
|
|
64
|
+
digits.push(carry % 58);
|
|
65
|
+
carry = (carry / 58) | 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let result = '';
|
|
69
|
+
for (let i = 0; i < buffer.length && buffer[i] === 0; i++) {
|
|
70
|
+
result += BASE58_ALPHABET[0]!;
|
|
71
|
+
}
|
|
72
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
73
|
+
result += BASE58_ALPHABET[digits[i]!]!;
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function base58Decode(str: string): Buffer {
|
|
79
|
+
const bytes = [0];
|
|
80
|
+
for (let i = 0; i < str.length; i++) {
|
|
81
|
+
let carry = BASE58_ALPHABET.indexOf(str[i]!);
|
|
82
|
+
if (carry === -1) {
|
|
83
|
+
throw new Error('Invalid base58 character');
|
|
84
|
+
}
|
|
85
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
86
|
+
carry += bytes[j]! * 58;
|
|
87
|
+
bytes[j] = carry & 0xff;
|
|
88
|
+
carry >>= 8;
|
|
89
|
+
}
|
|
90
|
+
while (carry > 0) {
|
|
91
|
+
bytes.push(carry & 0xff);
|
|
92
|
+
carry >>= 8;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let leadingZeros = 0;
|
|
96
|
+
for (let i = 0; i < str.length && str[i] === '1'; i++) {
|
|
97
|
+
leadingZeros++;
|
|
98
|
+
}
|
|
99
|
+
return Buffer.concat([
|
|
100
|
+
Buffer.alloc(leadingZeros, 0),
|
|
101
|
+
Buffer.from(bytes.reverse()),
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ===== PDA Derivation =====
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Find a program-derived address.
|
|
109
|
+
*/
|
|
110
|
+
export async function findProgramAddress(
|
|
111
|
+
seeds: Buffer[],
|
|
112
|
+
programId: string
|
|
113
|
+
): Promise<PDA> {
|
|
114
|
+
// Check cache
|
|
115
|
+
const cacheKey = `${programId}:${seeds.map(s => s.toString('hex')).join(':')}`;
|
|
116
|
+
const cached = pdaCache.get(cacheKey);
|
|
117
|
+
if (cached) {
|
|
118
|
+
return cached;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const programBytes = base58Decode(programId);
|
|
122
|
+
|
|
123
|
+
for (let bump = 255; bump > 0; bump--) {
|
|
124
|
+
try {
|
|
125
|
+
const address = await createProgramAddress(
|
|
126
|
+
[...seeds, Buffer.from([bump])],
|
|
127
|
+
programId
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const pda: PDA = { pubkey: address, bump };
|
|
131
|
+
pdaCache.set(cacheKey, pda);
|
|
132
|
+
return pda;
|
|
133
|
+
} catch {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
throw new Error('Unable to find valid PDA');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a program-derived address without bump.
|
|
143
|
+
*/
|
|
144
|
+
export async function createProgramAddress(
|
|
145
|
+
seeds: Buffer[],
|
|
146
|
+
programId: string
|
|
147
|
+
): Promise<Buffer> {
|
|
148
|
+
const programBytes = base58Decode(programId);
|
|
149
|
+
const data = concatBuffers(...seeds, programBytes);
|
|
150
|
+
const hash = await sha256(data);
|
|
151
|
+
|
|
152
|
+
// Check if on ed25519 curve (simplified check)
|
|
153
|
+
// In production, use proper ed25519 check
|
|
154
|
+
if (hash[31]! & 0x80) {
|
|
155
|
+
throw new Error('Invalid seeds: address on curve');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return hash;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ===== PumpFun PDAs =====
|
|
162
|
+
|
|
163
|
+
export async function getBondingCurvePDA(mint: string): Promise<PDA> {
|
|
164
|
+
const mintBytes = base58Decode(mint);
|
|
165
|
+
return findProgramAddress(
|
|
166
|
+
[Buffer.from('bonding-curve'), mintBytes],
|
|
167
|
+
PUMPFUN_PROGRAM_ID
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function getGlobalAccountPDA(): Promise<PDA> {
|
|
172
|
+
return findProgramAddress(
|
|
173
|
+
[Buffer.from('global')],
|
|
174
|
+
PUMPFUN_PROGRAM_ID
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function getFeeRecipientPDA(isMayhemMode: boolean = false): Promise<PDA> {
|
|
179
|
+
const seed = isMayhemMode ? 'fee_recipient_mayhem' : 'fee_recipient';
|
|
180
|
+
return findProgramAddress(
|
|
181
|
+
[Buffer.from(seed)],
|
|
182
|
+
PUMPFUN_PROGRAM_ID
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function getEventAuthorityPDA(): Promise<PDA> {
|
|
187
|
+
return findProgramAddress(
|
|
188
|
+
[Buffer.from('event')],
|
|
189
|
+
PUMPFUN_PROGRAM_ID
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function getUserVolumeAccumulatorPDA(user: string): Promise<PDA> {
|
|
194
|
+
const userBytes = base58Decode(user);
|
|
195
|
+
return findProgramAddress(
|
|
196
|
+
[Buffer.from('user_volume_accumulator'), userBytes],
|
|
197
|
+
PUMPFUN_PROGRAM_ID
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ===== PumpSwap PDAs =====
|
|
202
|
+
|
|
203
|
+
export async function getPumpSwapPoolPDA(baseMint: string, quoteMint: string): Promise<PDA> {
|
|
204
|
+
const baseBytes = base58Decode(baseMint);
|
|
205
|
+
const quoteBytes = base58Decode(quoteMint);
|
|
206
|
+
return findProgramAddress(
|
|
207
|
+
[Buffer.from('pool'), baseBytes, quoteBytes],
|
|
208
|
+
PUMPSWAP_PROGRAM_ID
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ===== Raydium PDAs =====
|
|
213
|
+
|
|
214
|
+
export async function getRaydiumAmmAuthorityPDA(): Promise<PDA> {
|
|
215
|
+
return findProgramAddress(
|
|
216
|
+
[Buffer.from('amm authority')],
|
|
217
|
+
RAYDIUM_AMM_V4_PROGRAM_ID
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function getRaydiumCpmmPoolPDA(
|
|
222
|
+
ammConfig: string,
|
|
223
|
+
baseMint: string,
|
|
224
|
+
quoteMint: string
|
|
225
|
+
): Promise<PDA> {
|
|
226
|
+
const ammBytes = base58Decode(ammConfig);
|
|
227
|
+
const baseBytes = base58Decode(baseMint);
|
|
228
|
+
const quoteBytes = base58Decode(quoteMint);
|
|
229
|
+
return findProgramAddress(
|
|
230
|
+
[Buffer.from('pool'), ammBytes, baseBytes, quoteBytes],
|
|
231
|
+
RAYDIUM_CPMM_PROGRAM_ID
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ===== Meteora PDAs =====
|
|
236
|
+
|
|
237
|
+
export async function getMeteoraPoolPDA(tokenAMint: string, tokenBMint: string): Promise<PDA> {
|
|
238
|
+
const aBytes = base58Decode(tokenAMint);
|
|
239
|
+
const bBytes = base58Decode(tokenBMint);
|
|
240
|
+
return findProgramAddress(
|
|
241
|
+
[Buffer.from('pool'), aBytes, bBytes],
|
|
242
|
+
METEORA_DAMM_V2_PROGRAM_ID
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ===== Associated Token Account =====
|
|
247
|
+
|
|
248
|
+
export async function getAssociatedTokenAddress(
|
|
249
|
+
wallet: string,
|
|
250
|
+
mint: string,
|
|
251
|
+
tokenProgram: string = TOKEN_PROGRAM_ID
|
|
252
|
+
): Promise<Buffer> {
|
|
253
|
+
const walletBytes = base58Decode(wallet);
|
|
254
|
+
const mintBytes = base58Decode(mint);
|
|
255
|
+
const programBytes = base58Decode(tokenProgram);
|
|
256
|
+
|
|
257
|
+
const pda = await findProgramAddress(
|
|
258
|
+
[walletBytes, programBytes, mintBytes],
|
|
259
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
260
|
+
);
|
|
261
|
+
return pda.pubkey;
|
|
262
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization Module for Sol Trade SDK
|
|
3
|
+
*
|
|
4
|
+
* Provides optimized transaction serialization based on Rust sol-trade-sdk:
|
|
5
|
+
* - Zero-allocation buffer pooling
|
|
6
|
+
* - Base58/Base64 encoding
|
|
7
|
+
* - Pooled buffer guards
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
// Constants
|
|
12
|
+
SERIALIZER_POOL_SIZE,
|
|
13
|
+
SERIALIZER_BUFFER_SIZE,
|
|
14
|
+
SERIALIZER_PREWARM_BUFFERS,
|
|
15
|
+
// Base58
|
|
16
|
+
encodeBase58,
|
|
17
|
+
decodeBase58,
|
|
18
|
+
// Transaction encoding
|
|
19
|
+
TransactionEncoding,
|
|
20
|
+
// Serializer
|
|
21
|
+
ZeroAllocSerializer,
|
|
22
|
+
Base64Encoder,
|
|
23
|
+
PooledTxBufferGuard,
|
|
24
|
+
// Functions
|
|
25
|
+
serializeTransactionSync,
|
|
26
|
+
serializeTransactionBatchSync,
|
|
27
|
+
getSerializerStats,
|
|
28
|
+
} from './serialization';
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction serialization module.
|
|
3
|
+
* Based on sol-trade-sdk Rust implementation with buffer pooling.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ===== Constants =====
|
|
7
|
+
|
|
8
|
+
export const SERIALIZER_POOL_SIZE = 10000;
|
|
9
|
+
export const SERIALIZER_BUFFER_SIZE = 256 * 1024;
|
|
10
|
+
export const SERIALIZER_PREWARM_BUFFERS = 64;
|
|
11
|
+
|
|
12
|
+
// ===== Base58 Encoding =====
|
|
13
|
+
|
|
14
|
+
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Encode bytes to base58 string
|
|
18
|
+
*/
|
|
19
|
+
export function encodeBase58(data: Uint8Array): string {
|
|
20
|
+
// Count leading zeros
|
|
21
|
+
let leadingZeros = 0;
|
|
22
|
+
for (let i = 0; i < data.length; i++) {
|
|
23
|
+
if (data[i] === 0) {
|
|
24
|
+
leadingZeros++;
|
|
25
|
+
} else {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Convert to base58
|
|
31
|
+
const digits: number[] = [0];
|
|
32
|
+
|
|
33
|
+
for (let i = leadingZeros; i < data.length; i++) {
|
|
34
|
+
let carry = data[i]!;
|
|
35
|
+
for (let j = 0; j < digits.length; j++) {
|
|
36
|
+
carry += digits[j]! << 8;
|
|
37
|
+
digits[j] = carry % 58;
|
|
38
|
+
carry = (carry / 58) | 0;
|
|
39
|
+
}
|
|
40
|
+
while (carry > 0) {
|
|
41
|
+
digits.push(carry % 58);
|
|
42
|
+
carry = (carry / 58) | 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Build result string
|
|
47
|
+
let result = '';
|
|
48
|
+
for (let i = 0; i < leadingZeros; i++) {
|
|
49
|
+
result += '1';
|
|
50
|
+
}
|
|
51
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
52
|
+
result += BASE58_ALPHABET[digits[i]!]!;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Decode base58 string to bytes
|
|
60
|
+
*/
|
|
61
|
+
export function decodeBase58(s: string): Uint8Array {
|
|
62
|
+
const bytes: number[] = [0];
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < s.length; i++) {
|
|
65
|
+
const char = s[i]!;
|
|
66
|
+
const value = BASE58_ALPHABET.indexOf(char);
|
|
67
|
+
if (value === -1) {
|
|
68
|
+
throw new Error(`Invalid base58 character: ${char}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let carry = value;
|
|
72
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
73
|
+
carry += bytes[j]! * 58;
|
|
74
|
+
bytes[j] = carry & 0xff;
|
|
75
|
+
carry = carry >> 8;
|
|
76
|
+
}
|
|
77
|
+
while (carry > 0) {
|
|
78
|
+
bytes.push(carry & 0xff);
|
|
79
|
+
carry = carry >> 8;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Count leading '1's
|
|
84
|
+
let leadingOnes = 0;
|
|
85
|
+
for (let i = 0; i < s.length; i++) {
|
|
86
|
+
if (s[i] === '1') {
|
|
87
|
+
leadingOnes++;
|
|
88
|
+
} else {
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Build result
|
|
94
|
+
const result = new Uint8Array(leadingOnes + bytes.length);
|
|
95
|
+
result.set(new Uint8Array(leadingOnes), 0);
|
|
96
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
97
|
+
result[leadingOnes + bytes.length - 1 - i] = bytes[i]!;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ===== Transaction Encoding =====
|
|
104
|
+
|
|
105
|
+
export enum TransactionEncoding {
|
|
106
|
+
BASE58 = 'base58',
|
|
107
|
+
BASE64 = 'base64',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ===== Zero-Allocation Serializer =====
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Uses a buffer pool to avoid runtime allocation.
|
|
114
|
+
* Based on Rust's ZeroAllocSerializer pattern.
|
|
115
|
+
*/
|
|
116
|
+
export class ZeroAllocSerializer {
|
|
117
|
+
private pool: Uint8Array[] = [];
|
|
118
|
+
private available = 0;
|
|
119
|
+
private capacity: number;
|
|
120
|
+
|
|
121
|
+
constructor(
|
|
122
|
+
poolSize: number = SERIALIZER_POOL_SIZE,
|
|
123
|
+
private bufferSize: number = SERIALIZER_BUFFER_SIZE,
|
|
124
|
+
prewarmBuffers: number = SERIALIZER_PREWARM_BUFFERS
|
|
125
|
+
) {
|
|
126
|
+
this.capacity = poolSize;
|
|
127
|
+
|
|
128
|
+
// Prewarm only a small hot set
|
|
129
|
+
const prewarmCount = Math.min(prewarmBuffers, poolSize);
|
|
130
|
+
for (let i = 0; i < prewarmCount; i++) {
|
|
131
|
+
this.pool.push(new Uint8Array(bufferSize));
|
|
132
|
+
this.available++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Serialize data using a pooled buffer
|
|
138
|
+
*/
|
|
139
|
+
serializeZeroAlloc(data: Uint8Array): Uint8Array {
|
|
140
|
+
let buf: Uint8Array;
|
|
141
|
+
if (this.pool.length > 0) {
|
|
142
|
+
buf = this.pool.pop()!;
|
|
143
|
+
this.available--;
|
|
144
|
+
} else {
|
|
145
|
+
buf = new Uint8Array(this.bufferSize);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Copy data
|
|
149
|
+
buf.set(data, 0);
|
|
150
|
+
return buf.slice(0, data.length);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Return a buffer to the pool
|
|
155
|
+
*/
|
|
156
|
+
returnBuffer(buf: Uint8Array): void {
|
|
157
|
+
if (this.pool.length < this.capacity) {
|
|
158
|
+
this.pool.push(buf);
|
|
159
|
+
this.available++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get pool statistics
|
|
165
|
+
*/
|
|
166
|
+
getPoolStats(): { available: number; capacity: number } {
|
|
167
|
+
return { available: this.available, capacity: this.capacity };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Global serializer instance
|
|
172
|
+
let globalSerializer: ZeroAllocSerializer | null = null;
|
|
173
|
+
|
|
174
|
+
function getGlobalSerializer(): ZeroAllocSerializer {
|
|
175
|
+
if (!globalSerializer) {
|
|
176
|
+
globalSerializer = new ZeroAllocSerializer();
|
|
177
|
+
}
|
|
178
|
+
return globalSerializer;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ===== Base64 Encoder =====
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Optimized base64 encoding
|
|
185
|
+
*/
|
|
186
|
+
export class Base64Encoder {
|
|
187
|
+
/**
|
|
188
|
+
* Encode data to base64
|
|
189
|
+
*/
|
|
190
|
+
static encode(data: Uint8Array): string {
|
|
191
|
+
// Use btoa for browser, Buffer for Node.js
|
|
192
|
+
if (typeof btoa === 'function') {
|
|
193
|
+
return btoa(String.fromCharCode(...data));
|
|
194
|
+
}
|
|
195
|
+
return Buffer.from(data).toString('base64');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Encode using pre-allocated buffer
|
|
200
|
+
*/
|
|
201
|
+
static encodeFast(data: Uint8Array): string {
|
|
202
|
+
return Base64Encoder.encode(data);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ===== PooledTxBufferGuard =====
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Returns buffer to pool on release.
|
|
210
|
+
* Use for automatic cleanup.
|
|
211
|
+
*/
|
|
212
|
+
export class PooledTxBufferGuard {
|
|
213
|
+
private buffer: Uint8Array | null;
|
|
214
|
+
private serializer: ZeroAllocSerializer;
|
|
215
|
+
|
|
216
|
+
constructor(data: Uint8Array, serializer?: ZeroAllocSerializer) {
|
|
217
|
+
this.serializer = serializer || getGlobalSerializer();
|
|
218
|
+
this.buffer = this.serializer.serializeZeroAlloc(data);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get the underlying buffer
|
|
223
|
+
*/
|
|
224
|
+
getBuffer(): Uint8Array {
|
|
225
|
+
if (!this.buffer) {
|
|
226
|
+
throw new Error('Buffer already released');
|
|
227
|
+
}
|
|
228
|
+
return this.buffer;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Return buffer to pool
|
|
233
|
+
*/
|
|
234
|
+
release(): void {
|
|
235
|
+
if (this.buffer) {
|
|
236
|
+
this.serializer.returnBuffer(this.buffer);
|
|
237
|
+
this.buffer = null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ===== Transaction Serialization =====
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Serialize a transaction using buffer pool.
|
|
246
|
+
* Returns encoded string.
|
|
247
|
+
*/
|
|
248
|
+
export function serializeTransactionSync(
|
|
249
|
+
transaction: Uint8Array,
|
|
250
|
+
encoding: TransactionEncoding
|
|
251
|
+
): string {
|
|
252
|
+
const serializer = getGlobalSerializer();
|
|
253
|
+
const serialized = serializer.serializeZeroAlloc(transaction);
|
|
254
|
+
try {
|
|
255
|
+
switch (encoding) {
|
|
256
|
+
case TransactionEncoding.BASE58:
|
|
257
|
+
return encodeBase58(serialized);
|
|
258
|
+
case TransactionEncoding.BASE64:
|
|
259
|
+
return Base64Encoder.encode(serialized);
|
|
260
|
+
default:
|
|
261
|
+
throw new Error(`Unsupported encoding: ${encoding}`);
|
|
262
|
+
}
|
|
263
|
+
} finally {
|
|
264
|
+
serializer.returnBuffer(serialized);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Serialize multiple transactions
|
|
270
|
+
*/
|
|
271
|
+
export function serializeTransactionBatchSync(
|
|
272
|
+
transactions: Uint8Array[],
|
|
273
|
+
encoding: TransactionEncoding
|
|
274
|
+
): string[] {
|
|
275
|
+
return transactions.map((tx) => serializeTransactionSync(tx, encoding));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ===== Get Statistics =====
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get global serializer statistics
|
|
282
|
+
*/
|
|
283
|
+
export function getSerializerStats(): {
|
|
284
|
+
available: number;
|
|
285
|
+
capacity: number;
|
|
286
|
+
} {
|
|
287
|
+
return getGlobalSerializer().getPoolStats();
|
|
288
|
+
}
|