@solana/kora 0.2.0-beta.6 → 0.2.1

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.
@@ -1,5 +1,5 @@
1
- import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
2
1
  import { KoraClient } from '../src/client.js';
2
+ import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
3
3
  import { getInstructionsFromBase64Message } from '../src/utils/transaction.js';
4
4
  // Mock fetch globally
5
5
  const mockFetch = jest.fn();
@@ -11,8 +11,8 @@ describe('KoraClient Unit Tests', () => {
11
11
  const mockSuccessfulResponse = (result) => {
12
12
  mockFetch.mockResolvedValueOnce({
13
13
  json: jest.fn().mockResolvedValueOnce({
14
- id: 1,
15
14
  jsonrpc: '2.0',
15
+ id: 1,
16
16
  result,
17
17
  }),
18
18
  });
@@ -20,9 +20,9 @@ describe('KoraClient Unit Tests', () => {
20
20
  const mockErrorResponse = (error) => {
21
21
  mockFetch.mockResolvedValueOnce({
22
22
  json: jest.fn().mockResolvedValueOnce({
23
- error,
24
- id: 1,
25
23
  jsonrpc: '2.0',
24
+ id: 1,
25
+ error,
26
26
  }),
27
27
  });
28
28
  };
@@ -78,81 +78,70 @@ describe('KoraClient Unit Tests', () => {
78
78
  describe('getConfig', () => {
79
79
  it('should return configuration', async () => {
80
80
  const mockConfig = {
81
- enabled_methods: {
82
- estimate_bundle_fee: true,
83
- estimate_transaction_fee: true,
84
- get_blockhash: true,
85
- get_config: true,
86
- get_payer_signer: true,
87
- get_supported_tokens: true,
88
- get_version: true,
89
- liveness: true,
90
- sign_and_send_bundle: true,
91
- sign_and_send_transaction: true,
92
- sign_bundle: true,
93
- sign_transaction: true,
94
- transfer_transaction: true,
95
- },
96
81
  fee_payers: ['test_fee_payer_address'],
97
82
  validation_config: {
83
+ max_allowed_lamports: 1000000,
84
+ max_signatures: 10,
85
+ price_source: 'Jupiter',
98
86
  allowed_programs: ['program1', 'program2'],
99
- allowed_spl_paid_tokens: ['spl_token1'],
100
87
  allowed_tokens: ['token1', 'token2'],
88
+ allowed_spl_paid_tokens: ['spl_token1'],
101
89
  disallowed_accounts: ['account1'],
102
90
  fee_payer_policy: {
103
- spl_token: {
104
- allow_approve: true,
105
- allow_burn: true,
106
- allow_close_account: true,
107
- allow_freeze_account: true,
108
- allow_initialize_account: true,
109
- allow_initialize_mint: true,
110
- allow_initialize_multisig: true,
111
- allow_mint_to: true,
112
- allow_revoke: true,
113
- allow_set_authority: true,
114
- allow_thaw_account: true,
115
- allow_transfer: true,
116
- },
117
91
  system: {
118
- allow_allocate: true,
92
+ allow_transfer: true,
119
93
  allow_assign: true,
120
94
  allow_create_account: true,
121
- allow_transfer: true,
95
+ allow_allocate: true,
122
96
  nonce: {
97
+ allow_initialize: true,
123
98
  allow_advance: true,
124
99
  allow_authorize: true,
125
- allow_initialize: true,
126
100
  allow_withdraw: true,
127
101
  },
128
102
  },
129
- token_2022: {
130
- allow_approve: true,
103
+ spl_token: {
104
+ allow_transfer: true,
131
105
  allow_burn: true,
132
106
  allow_close_account: true,
133
- allow_freeze_account: true,
134
- allow_initialize_account: true,
135
- allow_initialize_mint: true,
136
- allow_initialize_multisig: true,
137
- allow_mint_to: true,
107
+ allow_approve: true,
138
108
  allow_revoke: true,
139
109
  allow_set_authority: true,
110
+ allow_mint_to: true,
111
+ allow_freeze_account: true,
140
112
  allow_thaw_account: true,
113
+ },
114
+ token_2022: {
141
115
  allow_transfer: false,
116
+ allow_burn: true,
117
+ allow_close_account: true,
118
+ allow_approve: true,
119
+ allow_revoke: true,
120
+ allow_set_authority: true,
121
+ allow_mint_to: true,
122
+ allow_freeze_account: true,
123
+ allow_thaw_account: true,
142
124
  },
143
125
  },
144
- max_allowed_lamports: 1000000,
145
- max_signatures: 10,
146
126
  price: {
147
- margin: 0.1,
148
127
  type: 'margin',
128
+ margin: 0.1,
149
129
  },
150
- price_source: 'Jupiter',
151
130
  token2022: {
152
- blocked_account_extensions: ['account_extension1', 'account_extension2'],
153
131
  blocked_mint_extensions: ['extension1', 'extension2'],
132
+ blocked_account_extensions: ['account_extension1', 'account_extension2'],
154
133
  },
155
134
  },
135
+ enabled_methods: {
136
+ liveness: true,
137
+ estimate_transaction_fee: true,
138
+ get_supported_tokens: true,
139
+ sign_transaction: true,
140
+ sign_and_send_transaction: true,
141
+ transfer_transaction: true,
142
+ get_blockhash: true,
143
+ get_config: true,
144
+ },
156
145
  };
157
146
  await testSuccessfulRpcMethod('getConfig', () => client.getConfig(), mockConfig);
158
147
  });
@@ -165,14 +154,6 @@ describe('KoraClient Unit Tests', () => {
165
154
  await testSuccessfulRpcMethod('getBlockhash', () => client.getBlockhash(), mockResponse);
166
155
  });
167
156
  });
168
- describe('getVersion', () => {
169
- it('should return server version', async () => {
170
- const mockResponse = {
171
- version: '2.1.0-beta.0',
172
- };
173
- await testSuccessfulRpcMethod('getVersion', () => client.getVersion(), mockResponse);
174
- });
175
- });
176
157
  describe('getSupportedTokens', () => {
177
158
  it('should return supported tokens list', async () => {
178
159
  const mockResponse = {
@@ -184,15 +165,15 @@ describe('KoraClient Unit Tests', () => {
184
165
  describe('getPayerSigner', () => {
185
166
  it('should return payer signer and payment destination', async () => {
186
167
  const mockResponse = {
187
- payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
188
168
  signer_address: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
169
+ payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
189
170
  };
190
171
  await testSuccessfulRpcMethod('getPayerSigner', () => client.getPayerSigner(), mockResponse);
191
172
  });
192
173
  it('should return same address for signer and payment_destination when no separate paymaster', async () => {
193
174
  const mockResponse = {
194
- payment_address: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
195
175
  signer_address: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
176
+ payment_address: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
196
177
  };
197
178
  await testSuccessfulRpcMethod('getPayerSigner', () => client.getPayerSigner(), mockResponse);
198
179
  expect(mockResponse.signer_address).toBe(mockResponse.payment_address);
@@ -201,14 +182,14 @@ describe('KoraClient Unit Tests', () => {
201
182
  describe('estimateTransactionFee', () => {
202
183
  it('should estimate transaction fee', async () => {
203
184
  const request = {
204
- fee_token: 'SOL',
205
185
  transaction: 'base64_encoded_transaction',
186
+ fee_token: 'SOL',
206
187
  };
207
188
  const mockResponse = {
208
189
  fee_in_lamports: 5000,
209
190
  fee_in_token: 25,
210
- payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
211
191
  signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
192
+ payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
212
193
  };
213
194
  await testSuccessfulRpcMethod('estimateTransactionFee', () => client.estimateTransactionFee(request), mockResponse, request);
214
195
  });
@@ -238,139 +219,148 @@ describe('KoraClient Unit Tests', () => {
238
219
  await testSuccessfulRpcMethod('signAndSendTransaction', () => client.signAndSendTransaction(request), mockResponse, request);
239
220
  });
240
221
  });
241
- describe('signBundle', () => {
242
- it('should sign bundle of transactions', async () => {
222
+ describe('transferTransaction', () => {
223
+ it('should create transfer transaction', async () => {
243
224
  const request = {
244
- transactions: ['base64_tx_1', 'base64_tx_2'],
225
+ amount: 1000000,
226
+ token: 'SOL',
227
+ source: 'source_address',
228
+ destination: 'destination_address',
245
229
  };
246
230
  const mockResponse = {
247
- signed_transactions: ['base64_signed_tx_1', 'base64_signed_tx_2'],
231
+ transaction: 'base64_encoded_transaction',
232
+ message: 'Transfer transaction created',
233
+ blockhash: 'test_blockhash',
248
234
  signer_pubkey: 'test_signer_pubkey',
235
+ instructions: [],
249
236
  };
250
- await testSuccessfulRpcMethod('signBundle', () => client.signBundle(request), mockResponse, request);
251
- });
252
- it('should handle RPC error', async () => {
253
- const request = {
254
- transactions: ['base64_tx_1'],
255
- };
256
- const mockError = { code: -32000, message: 'Bundle validation failed' };
257
- mockErrorResponse(mockError);
258
- await expect(client.signBundle(request)).rejects.toThrow('RPC Error -32000: Bundle validation failed');
237
+ await testSuccessfulRpcMethod('transferTransaction', () => client.transferTransaction(request), mockResponse, request);
259
238
  });
260
- });
261
- describe('signAndSendBundle', () => {
262
- it('should sign and send bundle of transactions', async () => {
239
+ it('should parse instructions from transfer transaction message', async () => {
263
240
  const request = {
264
- transactions: ['base64_tx_1', 'base64_tx_2'],
241
+ amount: 1000000,
242
+ token: 'SOL',
243
+ source: 'source_address',
244
+ destination: 'destination_address',
265
245
  };
246
+ // This is a real base64 encoded message for testing
247
+ // In production, this would come from the RPC response
248
+ const mockMessage = 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDAAEMAgAAAAEAAAAAAAAA';
266
249
  const mockResponse = {
267
- bundle_uuid: 'test-bundle-uuid-123',
268
- signed_transactions: ['base64_signed_tx_1', 'base64_signed_tx_2'],
250
+ transaction: 'base64_encoded_transaction',
251
+ message: mockMessage,
252
+ blockhash: 'test_blockhash',
269
253
  signer_pubkey: 'test_signer_pubkey',
254
+ instructions: [],
270
255
  };
271
- await testSuccessfulRpcMethod('signAndSendBundle', () => client.signAndSendBundle(request), mockResponse, request);
272
- });
273
- it('should handle RPC error', async () => {
256
+ mockSuccessfulResponse(mockResponse);
257
+ const result = await client.transferTransaction(request);
258
+ expect(result.instructions).toBeDefined();
259
+ expect(Array.isArray(result.instructions)).toBe(true);
260
+ // The instructions array should be populated from the parsed message
261
+ expect(result.instructions).not.toBeNull();
262
+ });
263
+ it('should handle transfer transaction with empty message gracefully', async () => {
274
264
  const request = {
275
- transactions: ['base64_tx_1'],
265
+ amount: 1000000,
266
+ token: 'SOL',
267
+ source: 'source_address',
268
+ destination: 'destination_address',
276
269
  };
277
- const mockError = { code: -32000, message: 'Jito submission failed' };
278
- mockErrorResponse(mockError);
279
- await expect(client.signAndSendBundle(request)).rejects.toThrow('RPC Error -32000: Jito submission failed');
270
+ const mockResponse = {
271
+ transaction: 'base64_encoded_transaction',
272
+ message: '',
273
+ blockhash: 'test_blockhash',
274
+ signer_pubkey: 'test_signer_pubkey',
275
+ instructions: [],
276
+ };
277
+ mockSuccessfulResponse(mockResponse);
278
+ const result = await client.transferTransaction(request);
279
+ // Should handle empty message gracefully
280
+ expect(result.instructions).toEqual([]);
280
281
  });
281
282
  });
282
283
  describe('getPaymentInstruction', () => {
283
- const _mockConfig = {
284
- enabled_methods: {
285
- estimate_bundle_fee: true,
286
- estimate_transaction_fee: true,
287
- get_blockhash: true,
288
- get_config: true,
289
- get_payer_signer: true,
290
- get_supported_tokens: true,
291
- get_version: true,
292
- liveness: true,
293
- sign_and_send_bundle: true,
294
- sign_and_send_transaction: true,
295
- sign_bundle: true,
296
- sign_transaction: true,
297
- transfer_transaction: true,
298
- },
284
+ const mockConfig = {
299
285
  fee_payers: ['11111111111111111111111111111111'],
300
286
  validation_config: {
287
+ max_allowed_lamports: 1000000,
288
+ max_signatures: 10,
289
+ price_source: 'Jupiter',
301
290
  allowed_programs: ['program1'],
302
- allowed_spl_paid_tokens: ['4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'],
303
291
  allowed_tokens: ['token1'],
292
+ allowed_spl_paid_tokens: ['4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'],
304
293
  disallowed_accounts: [],
305
294
  fee_payer_policy: {
306
- spl_token: {
307
- allow_approve: true,
308
- allow_burn: true,
309
- allow_close_account: true,
310
- allow_freeze_account: true,
311
- allow_initialize_account: true,
312
- allow_initialize_mint: true,
313
- allow_initialize_multisig: true,
314
- allow_mint_to: true,
315
- allow_revoke: true,
316
- allow_set_authority: true,
317
- allow_thaw_account: true,
318
- allow_transfer: true,
319
- },
320
295
  system: {
321
- allow_allocate: true,
296
+ allow_transfer: true,
322
297
  allow_assign: true,
323
298
  allow_create_account: true,
324
- allow_transfer: true,
299
+ allow_allocate: true,
325
300
  nonce: {
301
+ allow_initialize: true,
326
302
  allow_advance: true,
327
303
  allow_authorize: true,
328
- allow_initialize: true,
329
304
  allow_withdraw: true,
330
305
  },
331
306
  },
332
- token_2022: {
333
- allow_approve: true,
307
+ spl_token: {
308
+ allow_transfer: true,
334
309
  allow_burn: true,
335
310
  allow_close_account: true,
336
- allow_freeze_account: true,
337
- allow_initialize_account: true,
338
- allow_initialize_mint: true,
339
- allow_initialize_multisig: true,
340
- allow_mint_to: true,
311
+ allow_approve: true,
341
312
  allow_revoke: true,
342
313
  allow_set_authority: true,
314
+ allow_mint_to: true,
315
+ allow_freeze_account: true,
343
316
  allow_thaw_account: true,
317
+ },
318
+ token_2022: {
344
319
  allow_transfer: true,
320
+ allow_burn: true,
321
+ allow_close_account: true,
322
+ allow_approve: true,
323
+ allow_revoke: true,
324
+ allow_set_authority: true,
325
+ allow_mint_to: true,
326
+ allow_freeze_account: true,
327
+ allow_thaw_account: true,
345
328
  },
346
329
  },
347
- max_allowed_lamports: 1000000,
348
- max_signatures: 10,
349
330
  price: {
350
- margin: 0.1,
351
331
  type: 'margin',
332
+ margin: 0.1,
352
333
  },
353
- price_source: 'Jupiter',
354
334
  token2022: {
355
- blocked_account_extensions: [],
356
335
  blocked_mint_extensions: [],
336
+ blocked_account_extensions: [],
357
337
  },
358
338
  },
339
+ enabled_methods: {
340
+ liveness: true,
341
+ estimate_transaction_fee: true,
342
+ get_supported_tokens: true,
343
+ sign_transaction: true,
344
+ sign_and_send_transaction: true,
345
+ transfer_transaction: true,
346
+ get_blockhash: true,
347
+ get_config: true,
348
+ },
359
349
  };
360
350
  const mockFeeEstimate = {
361
351
  fee_in_lamports: 5000,
362
352
  fee_in_token: 50000,
363
- payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
364
353
  signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
354
+ payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
365
355
  };
366
356
  // Create a mock base64-encoded transaction
367
357
  // This is a minimal valid transaction structure
368
358
  const mockTransactionBase64 = 'Aoq7ymA5OGP+gmDXiY5m3cYXlY2Rz/a/gFjOgt9ZuoCS7UzuiGGaEnW2OOtvHvMQHkkD7Z4LRF5B63ftu+1oZwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgECB1urjQEjgFgzqYhJ8IXJeSg4cJP1j1g2CJstOQTDchOKUzqH3PxgGW3c4V3vZV05A5Y30/MggOBs0Kd00s1JEwg5TaEeaV4+KL2y7fXIAuf6cN0ZQitbhY+G9ExtBSChspOXPgNcy9pYpETe4bmB+fg4bfZx1tnicA/kIyyubczAmbcIKIuniNOOQYG2ggKCz8NjEsHVezrWMatndu1wk6J5miGP26J6Vwp31AljiAajAFuP0D9mWJwSeFuA7J5rPwbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpd/O36SW02zRtNtqk6GFeip2+yBQsVTeSbLL4rWJRkd4CBgQCBQQBCgxAQg8AAAAAAAYGBAIFAwEKDBAnAAAAAAAABg==';
369
359
  const validRequest = {
360
+ transaction: mockTransactionBase64,
370
361
  fee_token: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
371
362
  source_wallet: '11111111111111111111111111111111',
372
363
  token_program_id: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
373
- transaction: mockTransactionBase64,
374
364
  };
375
365
  beforeEach(() => {
376
366
  // Mock console.log to avoid noise in tests
@@ -383,17 +373,16 @@ describe('KoraClient Unit Tests', () => {
383
373
  // Mock estimateTransactionFee call
384
374
  mockFetch.mockResolvedValueOnce({
385
375
  json: jest.fn().mockResolvedValueOnce({
386
- id: 1,
387
376
  jsonrpc: '2.0',
377
+ id: 1,
388
378
  result: mockFeeEstimate,
389
379
  }),
390
380
  });
391
381
  const result = await client.getPaymentInstruction(validRequest);
392
382
  expect(result).toEqual({
393
383
  original_transaction: validRequest.transaction,
394
- payment_address: mockFeeEstimate.payment_address,
395
- payment_amount: mockFeeEstimate.fee_in_token,
396
384
  payment_instruction: expect.objectContaining({
385
+ programAddress: TOKEN_PROGRAM_ADDRESS,
397
386
  accounts: [
398
387
  expect.objectContaining({
399
388
  role: 1, // writable
@@ -402,18 +391,15 @@ describe('KoraClient Unit Tests', () => {
402
391
  role: 1, // writable
403
392
  }), // Destination token account
404
393
  expect.objectContaining({
405
- // readonly-signer
394
+ role: 0, // readonly (plain address, no signer attached)
406
395
  address: validRequest.source_wallet,
407
- role: 2,
408
- signer: expect.objectContaining({
409
- address: validRequest.source_wallet,
410
- }),
411
396
  }), // Authority
412
397
  ],
413
398
  data: expect.any(Uint8Array),
414
- programAddress: TOKEN_PROGRAM_ADDRESS,
415
399
  }),
400
+ payment_amount: mockFeeEstimate.fee_in_token,
416
401
  payment_token: validRequest.fee_token,
402
+ payment_address: mockFeeEstimate.payment_address,
417
403
  signer_address: mockFeeEstimate.signer_pubkey,
418
404
  });
419
405
  // Verify only estimateTransactionFee was called
@@ -438,8 +424,8 @@ describe('KoraClient Unit Tests', () => {
438
424
  // Mock estimateTransactionFee call
439
425
  mockFetch.mockResolvedValueOnce({
440
426
  json: jest.fn().mockResolvedValueOnce({
441
- id: 1,
442
427
  jsonrpc: '2.0',
428
+ id: 1,
443
429
  result: mockFeeEstimate,
444
430
  }),
445
431
  });
@@ -462,9 +448,9 @@ describe('KoraClient Unit Tests', () => {
462
448
  const mockError = { code: -32602, message: 'Invalid transaction' };
463
449
  mockFetch.mockResolvedValueOnce({
464
450
  json: jest.fn().mockResolvedValueOnce({
465
- error: mockError,
466
- id: 1,
467
451
  jsonrpc: '2.0',
452
+ id: 1,
453
+ error: mockError,
468
454
  }),
469
455
  });
470
456
  await expect(client.getPaymentInstruction(validRequest)).rejects.toThrow('RPC Error -32602: Invalid transaction');
@@ -476,18 +462,18 @@ describe('KoraClient Unit Tests', () => {
476
462
  it('should return correct payment details in response', async () => {
477
463
  mockFetch.mockResolvedValueOnce({
478
464
  json: jest.fn().mockResolvedValueOnce({
479
- id: 1,
480
465
  jsonrpc: '2.0',
466
+ id: 1,
481
467
  result: mockFeeEstimate,
482
468
  }),
483
469
  });
484
470
  const result = await client.getPaymentInstruction(validRequest);
485
471
  expect(result).toMatchObject({
486
472
  original_transaction: validRequest.transaction,
487
- payment_address: mockFeeEstimate.payment_address,
488
- payment_amount: mockFeeEstimate.fee_in_token,
489
473
  payment_instruction: expect.any(Object),
474
+ payment_amount: mockFeeEstimate.fee_in_token,
490
475
  payment_token: validRequest.fee_token,
476
+ payment_address: mockFeeEstimate.payment_address,
491
477
  signer_address: mockFeeEstimate.signer_pubkey,
492
478
  });
493
479
  });
@@ -509,112 +495,28 @@ describe('KoraClient Unit Tests', () => {
509
495
  await expect(client.getConfig()).rejects.toThrow('RPC Error undefined: undefined');
510
496
  });
511
497
  });
512
- describe('reCAPTCHA Authentication', () => {
513
- it('should include x-recaptcha-token header when getRecaptchaToken callback is provided (sync)', async () => {
514
- const recaptchaClient = new KoraClient({
515
- getRecaptchaToken: () => 'test-recaptcha-token',
516
- rpcUrl: mockRpcUrl,
517
- });
518
- mockSuccessfulResponse({ version: '1.0.0' });
519
- await recaptchaClient.getVersion();
520
- expect(mockFetch).toHaveBeenCalledWith(mockRpcUrl, {
521
- body: JSON.stringify({
522
- id: 1,
523
- jsonrpc: '2.0',
524
- method: 'getVersion',
525
- params: undefined,
526
- }),
527
- headers: {
528
- 'Content-Type': 'application/json',
529
- 'x-recaptcha-token': 'test-recaptcha-token',
530
- },
531
- method: 'POST',
532
- });
533
- });
534
- it('should include x-recaptcha-token header when getRecaptchaToken callback returns Promise', async () => {
535
- const recaptchaClient = new KoraClient({
536
- getRecaptchaToken: () => Promise.resolve('async-recaptcha-token'),
537
- rpcUrl: mockRpcUrl,
538
- });
539
- mockSuccessfulResponse({ version: '1.0.0' });
540
- await recaptchaClient.getVersion();
541
- expect(mockFetch).toHaveBeenCalledWith(mockRpcUrl, {
542
- body: JSON.stringify({
543
- id: 1,
544
- jsonrpc: '2.0',
545
- method: 'getVersion',
546
- params: undefined,
547
- }),
548
- headers: {
549
- 'Content-Type': 'application/json',
550
- 'x-recaptcha-token': 'async-recaptcha-token',
551
- },
552
- method: 'POST',
553
- });
554
- });
555
- it('should NOT include x-recaptcha-token header when getRecaptchaToken is not provided', async () => {
556
- mockSuccessfulResponse({ version: '1.0.0' });
557
- await client.getVersion();
558
- expect(mockFetch).toHaveBeenCalledWith(mockRpcUrl, {
559
- body: JSON.stringify({
560
- id: 1,
561
- jsonrpc: '2.0',
562
- method: 'getVersion',
563
- params: undefined,
564
- }),
565
- headers: {
566
- 'Content-Type': 'application/json',
567
- },
568
- method: 'POST',
569
- });
570
- });
571
- it('should include x-recaptcha-token along with other auth headers', async () => {
572
- const combinedAuthClient = new KoraClient({
573
- apiKey: 'test-api-key',
574
- getRecaptchaToken: () => 'test-recaptcha-token',
575
- rpcUrl: mockRpcUrl,
576
- });
577
- mockSuccessfulResponse({ version: '1.0.0' });
578
- await combinedAuthClient.getVersion();
579
- const callArgs = mockFetch.mock.calls[0][1];
580
- expect(callArgs.headers).toMatchObject({
581
- 'Content-Type': 'application/json',
582
- 'x-api-key': 'test-api-key',
583
- 'x-recaptcha-token': 'test-recaptcha-token',
584
- });
585
- });
586
- it('should call getRecaptchaToken callback for each request', async () => {
587
- let callCount = 0;
588
- const recaptchaClient = new KoraClient({
589
- getRecaptchaToken: () => `token-${++callCount}`,
590
- rpcUrl: mockRpcUrl,
591
- });
592
- mockSuccessfulResponse({ version: '1.0.0' });
593
- await recaptchaClient.getVersion();
594
- mockSuccessfulResponse({ blockhash: 'test-blockhash' });
595
- await recaptchaClient.getBlockhash();
596
- expect(callCount).toBe(2);
597
- const calls = mockFetch.mock.calls;
598
- expect(calls[0][1].headers['x-recaptcha-token']).toBe('token-1');
599
- expect(calls[1][1].headers['x-recaptcha-token']).toBe('token-2');
600
- });
601
- it('should propagate errors when getRecaptchaToken callback throws', async () => {
602
- const recaptchaClient = new KoraClient({
603
- getRecaptchaToken: () => {
604
- throw new Error('reCAPTCHA failed to load');
605
- },
606
- rpcUrl: mockRpcUrl,
607
- });
608
- await expect(recaptchaClient.getVersion()).rejects.toThrow('reCAPTCHA failed to load');
609
- });
610
- it('should propagate errors when getRecaptchaToken returns rejected Promise', async () => {
611
- const recaptchaClient = new KoraClient({
612
- getRecaptchaToken: () => Promise.reject(new Error('Token generation failed')),
613
- rpcUrl: mockRpcUrl,
614
- });
615
- await expect(recaptchaClient.getVersion()).rejects.toThrow('Token generation failed');
616
- });
617
- });
498
+ // TODO: Add Authentication Tests (separate PR)
499
+ //
500
+ // describe('Authentication', () => {
501
+ // describe('API Key Authentication', () => {
502
+ // - Test that x-api-key header is included when apiKey is provided
503
+ // - Test requests work without apiKey when not provided
504
+ // - Test all RPC methods include the header
505
+ // });
506
+ //
507
+ // describe('HMAC Authentication', () => {
508
+ // - Test x-timestamp and x-hmac-signature headers are included when hmacSecret is provided
509
+ // - Test HMAC signature calculation is correct (SHA256 of timestamp + body)
510
+ // - Test timestamp is current (within reasonable bounds)
511
+ // - Test requests work without HMAC when not provided
512
+ // - Test all RPC methods include the headers
513
+ // });
514
+ //
515
+ // describe('Combined Authentication', () => {
516
+ // - Test both API key and HMAC headers are included when both are provided
517
+ // - Test headers are correctly combined
518
+ // });
519
+ // });
618
520
  });
619
521
  describe('Transaction Utils', () => {
620
522
  describe('getInstructionsFromBase64Message', () => {