@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.
- package/dist/src/client.d.ts +7 -201
- package/dist/src/client.js +18 -221
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/kit/executor.js +5 -18
- package/dist/src/kit/index.d.ts +1 -4
- package/dist/src/kit/index.js +2 -10
- package/dist/src/kit/payment.js +2 -3
- package/dist/src/kit/plugin.d.ts +31 -0
- package/dist/src/{plugin.js → kit/plugin.js} +18 -88
- package/dist/src/types/index.d.ts +79 -170
- package/dist/test/auth-setup.js +4 -4
- package/dist/test/integration.test.js +291 -168
- package/dist/test/kit-client.test.js +0 -18
- package/dist/test/plugin.test.js +71 -126
- package/dist/test/setup.d.ts +15 -8
- package/dist/test/setup.js +152 -52
- package/dist/test/unit.test.js +158 -256
- package/package.json +16 -8
- package/dist/src/plugin.d.ts +0 -85
package/dist/test/unit.test.js
CHANGED
|
@@ -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
|
-
|
|
92
|
+
allow_transfer: true,
|
|
119
93
|
allow_assign: true,
|
|
120
94
|
allow_create_account: true,
|
|
121
|
-
|
|
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
|
-
|
|
130
|
-
|
|
103
|
+
spl_token: {
|
|
104
|
+
allow_transfer: true,
|
|
131
105
|
allow_burn: true,
|
|
132
106
|
allow_close_account: true,
|
|
133
|
-
|
|
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('
|
|
242
|
-
it('should
|
|
222
|
+
describe('transferTransaction', () => {
|
|
223
|
+
it('should create transfer transaction', async () => {
|
|
243
224
|
const request = {
|
|
244
|
-
|
|
225
|
+
amount: 1000000,
|
|
226
|
+
token: 'SOL',
|
|
227
|
+
source: 'source_address',
|
|
228
|
+
destination: 'destination_address',
|
|
245
229
|
};
|
|
246
230
|
const mockResponse = {
|
|
247
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
268
|
-
|
|
250
|
+
transaction: 'base64_encoded_transaction',
|
|
251
|
+
message: mockMessage,
|
|
252
|
+
blockhash: 'test_blockhash',
|
|
269
253
|
signer_pubkey: 'test_signer_pubkey',
|
|
254
|
+
instructions: [],
|
|
270
255
|
};
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
265
|
+
amount: 1000000,
|
|
266
|
+
token: 'SOL',
|
|
267
|
+
source: 'source_address',
|
|
268
|
+
destination: 'destination_address',
|
|
276
269
|
};
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
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
|
|
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
|
-
|
|
296
|
+
allow_transfer: true,
|
|
322
297
|
allow_assign: true,
|
|
323
298
|
allow_create_account: true,
|
|
324
|
-
|
|
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
|
-
|
|
333
|
-
|
|
307
|
+
spl_token: {
|
|
308
|
+
allow_transfer: true,
|
|
334
309
|
allow_burn: true,
|
|
335
310
|
allow_close_account: true,
|
|
336
|
-
|
|
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
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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', () => {
|