@solana/kora 0.2.0-beta.1 → 0.2.0-beta.3
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 +22 -27
- package/dist/src/client.js +49 -56
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/plugin.d.ts +85 -0
- package/dist/src/plugin.js +175 -0
- package/dist/src/types/index.d.ts +277 -135
- package/dist/src/utils/transaction.js +1 -1
- package/dist/test/auth-setup.js +4 -4
- package/dist/test/integration.test.js +112 -131
- package/dist/test/plugin.test.d.ts +1 -0
- package/dist/test/plugin.test.js +442 -0
- package/dist/test/setup.d.ts +9 -9
- package/dist/test/setup.js +38 -35
- package/dist/test/unit.test.js +123 -138
- package/package.json +4 -3
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { createEmptyClient } from '@solana/kit';
|
|
2
|
+
import { koraPlugin } from '../src/plugin.js';
|
|
3
|
+
// Mock fetch globally
|
|
4
|
+
const mockFetch = jest.fn();
|
|
5
|
+
global.fetch = mockFetch;
|
|
6
|
+
describe('Kora Kit Plugin', () => {
|
|
7
|
+
const mockEndpoint = 'http://localhost:8080';
|
|
8
|
+
const mockConfig = {
|
|
9
|
+
endpoint: mockEndpoint,
|
|
10
|
+
};
|
|
11
|
+
// Helper to mock successful RPC response
|
|
12
|
+
const mockSuccessfulResponse = (result) => {
|
|
13
|
+
mockFetch.mockResolvedValueOnce({
|
|
14
|
+
json: jest.fn().mockResolvedValueOnce({
|
|
15
|
+
id: 1,
|
|
16
|
+
jsonrpc: '2.0',
|
|
17
|
+
result,
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
// Helper to mock error response
|
|
22
|
+
const mockErrorResponse = (error) => {
|
|
23
|
+
mockFetch.mockResolvedValueOnce({
|
|
24
|
+
json: jest.fn().mockResolvedValueOnce({
|
|
25
|
+
error,
|
|
26
|
+
id: 1,
|
|
27
|
+
jsonrpc: '2.0',
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
mockFetch.mockClear();
|
|
33
|
+
});
|
|
34
|
+
describe('Plugin Composition', () => {
|
|
35
|
+
it('should add kora property to client', () => {
|
|
36
|
+
const baseClient = { existing: 'property' };
|
|
37
|
+
const plugin = koraPlugin(mockConfig);
|
|
38
|
+
const enhanced = plugin(baseClient);
|
|
39
|
+
expect(enhanced.existing).toBe('property');
|
|
40
|
+
expect(enhanced.kora).toBeDefined();
|
|
41
|
+
expect(typeof enhanced.kora.getConfig).toBe('function');
|
|
42
|
+
expect(typeof enhanced.kora.getPayerSigner).toBe('function');
|
|
43
|
+
expect(typeof enhanced.kora.getBlockhash).toBe('function');
|
|
44
|
+
expect(typeof enhanced.kora.getVersion).toBe('function');
|
|
45
|
+
expect(typeof enhanced.kora.getSupportedTokens).toBe('function');
|
|
46
|
+
expect(typeof enhanced.kora.estimateTransactionFee).toBe('function');
|
|
47
|
+
expect(typeof enhanced.kora.estimateBundleFee).toBe('function');
|
|
48
|
+
expect(typeof enhanced.kora.signTransaction).toBe('function');
|
|
49
|
+
expect(typeof enhanced.kora.signAndSendTransaction).toBe('function');
|
|
50
|
+
expect(typeof enhanced.kora.signBundle).toBe('function');
|
|
51
|
+
expect(typeof enhanced.kora.signAndSendBundle).toBe('function');
|
|
52
|
+
expect(typeof enhanced.kora.getPaymentInstruction).toBe('function');
|
|
53
|
+
});
|
|
54
|
+
it('should work with empty client object', () => {
|
|
55
|
+
const plugin = koraPlugin(mockConfig);
|
|
56
|
+
const enhanced = plugin({});
|
|
57
|
+
expect(enhanced.kora).toBeDefined();
|
|
58
|
+
});
|
|
59
|
+
it('should support authentication options', () => {
|
|
60
|
+
const authConfig = {
|
|
61
|
+
apiKey: 'test-api-key',
|
|
62
|
+
endpoint: mockEndpoint,
|
|
63
|
+
hmacSecret: 'test-hmac-secret',
|
|
64
|
+
};
|
|
65
|
+
const plugin = koraPlugin(authConfig);
|
|
66
|
+
const enhanced = plugin({});
|
|
67
|
+
expect(enhanced.kora).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('Type Casting', () => {
|
|
71
|
+
let kora;
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
const plugin = koraPlugin(mockConfig);
|
|
74
|
+
const client = plugin({});
|
|
75
|
+
kora = client.kora;
|
|
76
|
+
});
|
|
77
|
+
describe('getConfig', () => {
|
|
78
|
+
it('should return Kit-typed Address arrays', async () => {
|
|
79
|
+
const rawResponse = {
|
|
80
|
+
enabled_methods: {
|
|
81
|
+
estimate_transaction_fee: true,
|
|
82
|
+
get_blockhash: true,
|
|
83
|
+
get_config: true,
|
|
84
|
+
get_supported_tokens: true,
|
|
85
|
+
liveness: true,
|
|
86
|
+
sign_and_send_transaction: true,
|
|
87
|
+
sign_transaction: true,
|
|
88
|
+
transfer_transaction: true,
|
|
89
|
+
},
|
|
90
|
+
fee_payers: ['DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7'],
|
|
91
|
+
validation_config: {
|
|
92
|
+
allowed_programs: ['11111111111111111111111111111111'],
|
|
93
|
+
allowed_spl_paid_tokens: ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
|
|
94
|
+
allowed_tokens: ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
|
|
95
|
+
disallowed_accounts: [],
|
|
96
|
+
fee_payer_policy: {
|
|
97
|
+
spl_token: {
|
|
98
|
+
allow_approve: true,
|
|
99
|
+
allow_burn: true,
|
|
100
|
+
allow_close_account: true,
|
|
101
|
+
allow_freeze_account: true,
|
|
102
|
+
allow_mint_to: true,
|
|
103
|
+
allow_revoke: true,
|
|
104
|
+
allow_set_authority: true,
|
|
105
|
+
allow_thaw_account: true,
|
|
106
|
+
allow_transfer: true,
|
|
107
|
+
},
|
|
108
|
+
system: {
|
|
109
|
+
allow_allocate: true,
|
|
110
|
+
allow_assign: true,
|
|
111
|
+
allow_create_account: true,
|
|
112
|
+
allow_transfer: true,
|
|
113
|
+
nonce: {
|
|
114
|
+
allow_advance: true,
|
|
115
|
+
allow_authorize: true,
|
|
116
|
+
allow_initialize: true,
|
|
117
|
+
allow_withdraw: true,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
token_2022: {
|
|
121
|
+
allow_approve: true,
|
|
122
|
+
allow_burn: true,
|
|
123
|
+
allow_close_account: true,
|
|
124
|
+
allow_freeze_account: true,
|
|
125
|
+
allow_mint_to: true,
|
|
126
|
+
allow_revoke: true,
|
|
127
|
+
allow_set_authority: true,
|
|
128
|
+
allow_thaw_account: true,
|
|
129
|
+
allow_transfer: true,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
max_allowed_lamports: 1000000,
|
|
133
|
+
max_signatures: 10,
|
|
134
|
+
price: { margin: 0.1, type: 'margin' },
|
|
135
|
+
price_source: 'Jupiter',
|
|
136
|
+
token2022: {
|
|
137
|
+
blocked_account_extensions: [],
|
|
138
|
+
blocked_mint_extensions: [],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
mockSuccessfulResponse(rawResponse);
|
|
143
|
+
const result = await kora.getConfig();
|
|
144
|
+
// Verify type casting - these should be Address types
|
|
145
|
+
expect(result.fee_payers).toHaveLength(1);
|
|
146
|
+
expect(result.fee_payers[0]).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
147
|
+
expect(result.validation_config.allowed_programs).toHaveLength(1);
|
|
148
|
+
expect(result.validation_config.allowed_programs[0]).toBe('11111111111111111111111111111111');
|
|
149
|
+
expect(result.validation_config.allowed_tokens).toHaveLength(1);
|
|
150
|
+
expect(result.validation_config.allowed_tokens[0]).toBe('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('getPayerSigner', () => {
|
|
154
|
+
it('should return Kit-typed Address fields', async () => {
|
|
155
|
+
const rawResponse = {
|
|
156
|
+
payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
157
|
+
signer_address: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
158
|
+
};
|
|
159
|
+
mockSuccessfulResponse(rawResponse);
|
|
160
|
+
const result = await kora.getPayerSigner();
|
|
161
|
+
// Type assertion - these should be Address types
|
|
162
|
+
const signerAddr = result.signer_address;
|
|
163
|
+
const paymentAddr = result.payment_address;
|
|
164
|
+
expect(signerAddr).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
165
|
+
expect(paymentAddr).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe('getBlockhash', () => {
|
|
169
|
+
it('should return Kit-typed Blockhash field', async () => {
|
|
170
|
+
const rawResponse = {
|
|
171
|
+
blockhash: '4NxM2D4kQcipkzMWBWQME5YSVnj5kT8QKA7rvb3rKLvE',
|
|
172
|
+
};
|
|
173
|
+
mockSuccessfulResponse(rawResponse);
|
|
174
|
+
const result = await kora.getBlockhash();
|
|
175
|
+
// Type assertion - should be Blockhash type
|
|
176
|
+
const hash = result.blockhash;
|
|
177
|
+
expect(hash).toBe('4NxM2D4kQcipkzMWBWQME5YSVnj5kT8QKA7rvb3rKLvE');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe('getVersion', () => {
|
|
181
|
+
it('should return version string', async () => {
|
|
182
|
+
const rawResponse = {
|
|
183
|
+
version: '2.0.0',
|
|
184
|
+
};
|
|
185
|
+
mockSuccessfulResponse(rawResponse);
|
|
186
|
+
const result = await kora.getVersion();
|
|
187
|
+
expect(result.version).toBe('2.0.0');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
describe('getSupportedTokens', () => {
|
|
191
|
+
it('should return Kit-typed Address array', async () => {
|
|
192
|
+
const rawResponse = {
|
|
193
|
+
tokens: [
|
|
194
|
+
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
195
|
+
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
mockSuccessfulResponse(rawResponse);
|
|
199
|
+
const result = await kora.getSupportedTokens();
|
|
200
|
+
// Type assertion - these should be Address types
|
|
201
|
+
expect(result.tokens).toHaveLength(2);
|
|
202
|
+
const token0 = result.tokens[0];
|
|
203
|
+
const token1 = result.tokens[1];
|
|
204
|
+
expect(token0).toBe('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
|
|
205
|
+
expect(token1).toBe('Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB');
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
describe('estimateTransactionFee', () => {
|
|
209
|
+
it('should return Kit-typed Address fields', async () => {
|
|
210
|
+
const rawResponse = {
|
|
211
|
+
fee_in_lamports: 5000,
|
|
212
|
+
fee_in_token: 50,
|
|
213
|
+
payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
214
|
+
signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
215
|
+
};
|
|
216
|
+
mockSuccessfulResponse(rawResponse);
|
|
217
|
+
const result = await kora.estimateTransactionFee({
|
|
218
|
+
fee_token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
219
|
+
transaction: 'base64EncodedTransaction',
|
|
220
|
+
});
|
|
221
|
+
// Type assertions
|
|
222
|
+
const signerPubkey = result.signer_pubkey;
|
|
223
|
+
const paymentAddr = result.payment_address;
|
|
224
|
+
expect(result.fee_in_lamports).toBe(5000);
|
|
225
|
+
expect(result.fee_in_token).toBe(50);
|
|
226
|
+
expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
227
|
+
expect(paymentAddr).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
describe('estimateBundleFee', () => {
|
|
231
|
+
it('should return Kit-typed Address fields for bundle', async () => {
|
|
232
|
+
const rawResponse = {
|
|
233
|
+
fee_in_lamports: 15000,
|
|
234
|
+
fee_in_token: 150,
|
|
235
|
+
payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
236
|
+
signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
237
|
+
};
|
|
238
|
+
mockSuccessfulResponse(rawResponse);
|
|
239
|
+
const result = await kora.estimateBundleFee({
|
|
240
|
+
fee_token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
241
|
+
transactions: ['base64Tx1', 'base64Tx2', 'base64Tx3'],
|
|
242
|
+
});
|
|
243
|
+
// Type assertions
|
|
244
|
+
const signerPubkey = result.signer_pubkey;
|
|
245
|
+
const paymentAddr = result.payment_address;
|
|
246
|
+
expect(result.fee_in_lamports).toBe(15000);
|
|
247
|
+
expect(result.fee_in_token).toBe(150);
|
|
248
|
+
expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
249
|
+
expect(paymentAddr).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
describe('signTransaction', () => {
|
|
253
|
+
it('should return Kit-typed response with Base64EncodedWireTransaction', async () => {
|
|
254
|
+
const rawResponse = {
|
|
255
|
+
signed_transaction: 'base64SignedTransaction',
|
|
256
|
+
signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
257
|
+
};
|
|
258
|
+
mockSuccessfulResponse(rawResponse);
|
|
259
|
+
const result = await kora.signTransaction({
|
|
260
|
+
transaction: 'base64EncodedTransaction',
|
|
261
|
+
});
|
|
262
|
+
// Type assertions - verify Kit types
|
|
263
|
+
const signedTx = result.signed_transaction;
|
|
264
|
+
const signerPubkey = result.signer_pubkey;
|
|
265
|
+
expect(signedTx).toBe('base64SignedTransaction');
|
|
266
|
+
expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
describe('signAndSendTransaction', () => {
|
|
270
|
+
it('should return Kit-typed response with Signature and Base64EncodedWireTransaction', async () => {
|
|
271
|
+
// Use a valid base58 signature (88 characters, valid base58 alphabet)
|
|
272
|
+
const mockSignature = '5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW';
|
|
273
|
+
const rawResponse = {
|
|
274
|
+
signature: mockSignature,
|
|
275
|
+
signed_transaction: 'base64SignedTransaction',
|
|
276
|
+
signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
277
|
+
};
|
|
278
|
+
mockSuccessfulResponse(rawResponse);
|
|
279
|
+
const result = await kora.signAndSendTransaction({
|
|
280
|
+
transaction: 'base64EncodedTransaction',
|
|
281
|
+
});
|
|
282
|
+
// Type assertions - verify Kit types
|
|
283
|
+
const sig = result.signature;
|
|
284
|
+
const signedTx = result.signed_transaction;
|
|
285
|
+
const signerPubkey = result.signer_pubkey;
|
|
286
|
+
expect(sig).toBe(mockSignature);
|
|
287
|
+
expect(signedTx).toBe('base64SignedTransaction');
|
|
288
|
+
expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
describe('signBundle', () => {
|
|
292
|
+
it('should return Kit-typed response with Base64EncodedWireTransaction array', async () => {
|
|
293
|
+
const rawResponse = {
|
|
294
|
+
signed_transactions: ['base64SignedTx1', 'base64SignedTx2'],
|
|
295
|
+
signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
296
|
+
};
|
|
297
|
+
mockSuccessfulResponse(rawResponse);
|
|
298
|
+
const result = await kora.signBundle({
|
|
299
|
+
transactions: ['base64Tx1', 'base64Tx2'],
|
|
300
|
+
});
|
|
301
|
+
// Type assertions - verify Kit types
|
|
302
|
+
const signedTxs = result.signed_transactions;
|
|
303
|
+
const signerPubkey = result.signer_pubkey;
|
|
304
|
+
expect(signedTxs).toHaveLength(2);
|
|
305
|
+
expect(signedTxs[0]).toBe('base64SignedTx1');
|
|
306
|
+
expect(signedTxs[1]).toBe('base64SignedTx2');
|
|
307
|
+
expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
describe('signAndSendBundle', () => {
|
|
311
|
+
it('should return Kit-typed response with Base64EncodedWireTransaction array and bundle UUID', async () => {
|
|
312
|
+
const rawResponse = {
|
|
313
|
+
bundle_uuid: 'jito-bundle-uuid-12345',
|
|
314
|
+
signed_transactions: ['base64SignedTx1', 'base64SignedTx2'],
|
|
315
|
+
signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
316
|
+
};
|
|
317
|
+
mockSuccessfulResponse(rawResponse);
|
|
318
|
+
const result = await kora.signAndSendBundle({
|
|
319
|
+
transactions: ['base64Tx1', 'base64Tx2'],
|
|
320
|
+
});
|
|
321
|
+
// Type assertions - verify Kit types
|
|
322
|
+
const signedTxs = result.signed_transactions;
|
|
323
|
+
const signerPubkey = result.signer_pubkey;
|
|
324
|
+
expect(signedTxs).toHaveLength(2);
|
|
325
|
+
expect(signedTxs[0]).toBe('base64SignedTx1');
|
|
326
|
+
expect(signedTxs[1]).toBe('base64SignedTx2');
|
|
327
|
+
expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
328
|
+
expect(result.bundle_uuid).toBe('jito-bundle-uuid-12345');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
describe('getPaymentInstruction', () => {
|
|
332
|
+
it('should return Kit-typed response with Base64EncodedWireTransaction and Address fields', async () => {
|
|
333
|
+
const mockFeeEstimate = {
|
|
334
|
+
fee_in_lamports: 5000,
|
|
335
|
+
fee_in_token: 50000,
|
|
336
|
+
payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
337
|
+
signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
338
|
+
};
|
|
339
|
+
const testTx = 'Aoq7ymA5OGP+gmDXiY5m3cYXlY2Rz/a/gFjOgt9ZuoCS7UzuiGGaEnW2OOtvHvMQHkkD7Z4LRF5B63ftu+1oZwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgECB1urjQEjgFgzqYhJ8IXJeSg4cJP1j1g2CJstOQTDchOKUzqH3PxgGW3c4V3vZV05A5Y30/MggOBs0Kd00s1JEwg5TaEeaV4+KL2y7fXIAuf6cN0ZQitbhY+G9ExtBSChspOXPgNcy9pYpETe4bmB+fg4bfZx1tnicA/kIyyubczAmbcIKIuniNOOQYG2ggKCz8NjEsHVezrWMatndu1wk6J5miGP26J6Vwp31AljiAajAFuP0D9mWJwSeFuA7J5rPwbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpd/O36SW02zRtNtqk6GFeip2+yBQsVTeSbLL4rWJRkd4CBgQCBQQBCgxAQg8AAAAAAAYGBAIFAwEKDBAnAAAAAAAABg==';
|
|
340
|
+
mockSuccessfulResponse(mockFeeEstimate);
|
|
341
|
+
const result = await kora.getPaymentInstruction({
|
|
342
|
+
fee_token: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
|
|
343
|
+
source_wallet: '11111111111111111111111111111111',
|
|
344
|
+
token_program_id: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
|
|
345
|
+
transaction: testTx,
|
|
346
|
+
});
|
|
347
|
+
// Type assertions - verify Kit types
|
|
348
|
+
const originalTx = result.original_transaction;
|
|
349
|
+
const paymentToken = result.payment_token;
|
|
350
|
+
const paymentAddr = result.payment_address;
|
|
351
|
+
const signerAddr = result.signer_address;
|
|
352
|
+
expect(originalTx).toBe(testTx);
|
|
353
|
+
expect(paymentToken).toBe('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU');
|
|
354
|
+
expect(paymentAddr).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
355
|
+
expect(signerAddr).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
356
|
+
expect(result.payment_amount).toBe(50000);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
describe('Error Handling', () => {
|
|
361
|
+
let kora;
|
|
362
|
+
beforeEach(() => {
|
|
363
|
+
const plugin = koraPlugin(mockConfig);
|
|
364
|
+
const client = plugin({});
|
|
365
|
+
kora = client.kora;
|
|
366
|
+
});
|
|
367
|
+
it('should propagate RPC errors', async () => {
|
|
368
|
+
mockErrorResponse({ code: -32601, message: 'Method not found' });
|
|
369
|
+
await expect(kora.getConfig()).rejects.toThrow('RPC Error -32601: Method not found');
|
|
370
|
+
});
|
|
371
|
+
it('should propagate network errors', async () => {
|
|
372
|
+
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
373
|
+
await expect(kora.getConfig()).rejects.toThrow('Network error');
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
describe('KoraApi Type Export', () => {
|
|
377
|
+
it('should export KoraApi type correctly', () => {
|
|
378
|
+
// This test verifies the KoraApi type is correctly exported
|
|
379
|
+
const plugin = koraPlugin(mockConfig);
|
|
380
|
+
const client = plugin({});
|
|
381
|
+
// Type check - assign to KoraApi type
|
|
382
|
+
const api = client.kora;
|
|
383
|
+
expect(api).toBeDefined();
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
describe('createEmptyClient Integration', () => {
|
|
387
|
+
it('should initialize kora property on Kit client', () => {
|
|
388
|
+
const client = createEmptyClient().use(koraPlugin(mockConfig));
|
|
389
|
+
expect(client).toHaveProperty('kora');
|
|
390
|
+
expect(client.kora).toBeDefined();
|
|
391
|
+
});
|
|
392
|
+
it('should expose all Kora RPC methods', () => {
|
|
393
|
+
const client = createEmptyClient().use(koraPlugin(mockConfig));
|
|
394
|
+
expect(typeof client.kora.getConfig).toBe('function');
|
|
395
|
+
expect(typeof client.kora.getPayerSigner).toBe('function');
|
|
396
|
+
expect(typeof client.kora.getBlockhash).toBe('function');
|
|
397
|
+
expect(typeof client.kora.getVersion).toBe('function');
|
|
398
|
+
expect(typeof client.kora.getSupportedTokens).toBe('function');
|
|
399
|
+
expect(typeof client.kora.estimateTransactionFee).toBe('function');
|
|
400
|
+
expect(typeof client.kora.estimateBundleFee).toBe('function');
|
|
401
|
+
expect(typeof client.kora.signTransaction).toBe('function');
|
|
402
|
+
expect(typeof client.kora.signAndSendTransaction).toBe('function');
|
|
403
|
+
expect(typeof client.kora.signBundle).toBe('function');
|
|
404
|
+
expect(typeof client.kora.signAndSendBundle).toBe('function');
|
|
405
|
+
expect(typeof client.kora.getPaymentInstruction).toBe('function');
|
|
406
|
+
});
|
|
407
|
+
it('should work with authentication config', () => {
|
|
408
|
+
const authConfig = {
|
|
409
|
+
apiKey: 'test-api-key',
|
|
410
|
+
endpoint: mockEndpoint,
|
|
411
|
+
hmacSecret: 'test-hmac-secret',
|
|
412
|
+
};
|
|
413
|
+
const client = createEmptyClient().use(koraPlugin(authConfig));
|
|
414
|
+
expect(client.kora).toBeDefined();
|
|
415
|
+
expect(typeof client.kora.getConfig).toBe('function');
|
|
416
|
+
});
|
|
417
|
+
it('should compose with other plugins', () => {
|
|
418
|
+
// Simulate another plugin that adds a different property
|
|
419
|
+
const otherPlugin = (c) => ({
|
|
420
|
+
...c,
|
|
421
|
+
other: { foo: () => 'bar' },
|
|
422
|
+
});
|
|
423
|
+
const client = createEmptyClient().use(koraPlugin(mockConfig)).use(otherPlugin);
|
|
424
|
+
// Both plugins should be available
|
|
425
|
+
expect(client.kora).toBeDefined();
|
|
426
|
+
expect(client.other).toBeDefined();
|
|
427
|
+
expect(typeof client.kora.getConfig).toBe('function');
|
|
428
|
+
expect(client.other.foo()).toBe('bar');
|
|
429
|
+
});
|
|
430
|
+
it('should call RPC methods correctly', async () => {
|
|
431
|
+
const mockResponse = {
|
|
432
|
+
payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
433
|
+
signer_address: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
|
|
434
|
+
};
|
|
435
|
+
mockSuccessfulResponse(mockResponse);
|
|
436
|
+
const client = createEmptyClient().use(koraPlugin(mockConfig));
|
|
437
|
+
const result = await client.kora.getPayerSigner();
|
|
438
|
+
expect(result.signer_address).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
439
|
+
expect(result.payment_address).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
});
|
package/dist/test/setup.d.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { Commitment, KeyPairSigner
|
|
1
|
+
import { Address, Commitment, KeyPairSigner } from '@solana/kit';
|
|
2
2
|
import { KoraClient } from '../src/index.js';
|
|
3
3
|
interface TestSuite {
|
|
4
|
+
destinationAddress: Address<string>;
|
|
5
|
+
koraAddress: Address<string>;
|
|
4
6
|
koraClient: KoraClient;
|
|
5
7
|
koraRpcUrl: string;
|
|
6
8
|
testWallet: KeyPairSigner<string>;
|
|
7
9
|
usdcMint: Address<string>;
|
|
8
|
-
destinationAddress: Address<string>;
|
|
9
|
-
koraAddress: Address<string>;
|
|
10
10
|
}
|
|
11
11
|
export declare function loadEnvironmentVariables(): {
|
|
12
|
-
|
|
12
|
+
commitment: Commitment;
|
|
13
|
+
destinationAddress: Address<string>;
|
|
13
14
|
koraAddress: Address<string>;
|
|
15
|
+
koraRpcUrl: string;
|
|
14
16
|
koraSignerType: string;
|
|
15
|
-
commitment: Commitment;
|
|
16
|
-
tokenDecimals: number;
|
|
17
|
-
tokenDropAmount: number;
|
|
18
17
|
solDropAmount: bigint;
|
|
19
18
|
solanaRpcUrl: string;
|
|
20
19
|
solanaWsUrl: string;
|
|
21
|
-
testWalletSecret: string;
|
|
22
20
|
testUsdcMintSecret: string;
|
|
23
|
-
|
|
21
|
+
testWalletSecret: string;
|
|
22
|
+
tokenDecimals: number;
|
|
23
|
+
tokenDropAmount: number;
|
|
24
24
|
};
|
|
25
25
|
declare function setupTestSuite(): Promise<TestSuite>;
|
|
26
26
|
export default setupTestSuite;
|
package/dist/test/setup.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
+
import { airdropFactory, appendTransactionMessageInstructions, assertIsAddress, assertIsSendableTransaction, assertIsTransactionWithBlockhashLifetime, createKeyPairSignerFromBytes, createSolanaRpc, createSolanaRpcSubscriptions, createTransactionMessage, getBase58Encoder, getSignatureFromTransaction, lamports, pipe, sendAndConfirmTransactionFactory, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, signTransactionMessageWithSigners, } from '@solana/kit';
|
|
2
|
+
import { MAX_COMPUTE_UNIT_LIMIT, updateOrAppendSetComputeUnitLimitInstruction, updateOrAppendSetComputeUnitPriceInstruction, } from '@solana-program/compute-budget';
|
|
1
3
|
import { getCreateAccountInstruction } from '@solana-program/system';
|
|
2
4
|
import { findAssociatedTokenPda, getCreateAssociatedTokenIdempotentInstructionAsync, getInitializeMintInstruction, getMintSize, getMintToInstruction, TOKEN_PROGRAM_ADDRESS, } from '@solana-program/token';
|
|
3
|
-
import { airdropFactory, createSolanaRpc, createSolanaRpcSubscriptions, lamports, sendAndConfirmTransactionFactory, pipe, createTransactionMessage, setTransactionMessageLifetimeUsingBlockhash, setTransactionMessageFeePayerSigner, appendTransactionMessageInstructions, signTransactionMessageWithSigners, getSignatureFromTransaction, assertIsAddress, createKeyPairSignerFromBytes, getBase58Encoder, assertIsSendableTransaction, assertIsTransactionWithBlockhashLifetime, } from '@solana/kit';
|
|
4
|
-
import { updateOrAppendSetComputeUnitLimitInstruction, updateOrAppendSetComputeUnitPriceInstruction, MAX_COMPUTE_UNIT_LIMIT, } from '@solana-program/compute-budget';
|
|
5
5
|
import { config } from 'dotenv';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { KoraClient } from '../src/index.js';
|
|
8
8
|
config({ path: path.resolve(process.cwd(), '.env') });
|
|
9
9
|
const DEFAULTS = {
|
|
10
|
+
COMMITMENT: 'processed',
|
|
10
11
|
DECIMALS: 6,
|
|
11
|
-
|
|
12
|
+
// Make sure this matches the USDC mint in kora.toml (9BgeTKqmFsPVnfYscfM6NvsgmZxei7XfdciShQ6D3bxJ)
|
|
13
|
+
DESTINATION_ADDRESS: 'AVmDft8deQEo78bRKcGN5ZMf3hyjeLBK4Rd4xGB46yQM',
|
|
14
|
+
// DO NOT USE THESE KEYPAIRS IN PRODUCTION, TESTING KEYPAIRS ONLY
|
|
15
|
+
KORA_ADDRESS: '7AqpcUvgJ7Kh1VmJZ44rWp2XDow33vswo9VK9VqpPU2d',
|
|
12
16
|
KORA_RPC_URL: 'http://localhost:8080/',
|
|
17
|
+
KORA_SIGNER_TYPE: 'memory',
|
|
18
|
+
// Make sure this matches the kora-rpc signer address on launch (root .env)
|
|
19
|
+
SENDER_SECRET: 'tzgfgSWTE3KUA6qfRoFYLaSfJm59uUeZRDy4ybMrLn1JV2drA1mftiaEcVFvq1Lok6h6EX2C4Y9kSKLvQWyMpS5',
|
|
13
20
|
SOLANA_RPC_URL: 'http://127.0.0.1:8899',
|
|
14
21
|
SOLANA_WS_URL: 'ws://127.0.0.1:8900',
|
|
15
|
-
COMMITMENT: 'processed',
|
|
16
22
|
SOL_DROP_AMOUNT: 1_000_000_000,
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
TEST_USDC_MINT_SECRET: '59kKmXphL5UJANqpFFjtH17emEq3oRNmYsx6a3P3vSGJRmhMgVdzH77bkNEi9bArRViT45e8L2TsuPxKNFoc3Qfg', // Make sure this matches the USDC mint in kora.toml (9BgeTKqmFsPVnfYscfM6NvsgmZxei7XfdciShQ6D3bxJ)
|
|
21
|
-
DESTINATION_ADDRESS: 'AVmDft8deQEo78bRKcGN5ZMf3hyjeLBK4Rd4xGB46yQM',
|
|
22
|
-
KORA_SIGNER_TYPE: 'memory', // Default signer type
|
|
23
|
+
// HhA5j2rRiPbMrpF2ZD36r69FyZf3zWmEHRNSZbbNdVjf
|
|
24
|
+
TEST_USDC_MINT_SECRET: '59kKmXphL5UJANqpFFjtH17emEq3oRNmYsx6a3P3vSGJRmhMgVdzH77bkNEi9bArRViT45e8L2TsuPxKNFoc3Qfg',
|
|
25
|
+
TOKEN_DROP_AMOUNT: 100_000, // Default signer type
|
|
23
26
|
};
|
|
24
27
|
const createKeyPairSignerFromB58Secret = async (b58Secret) => {
|
|
25
28
|
const base58Encoder = getBase58Encoder();
|
|
@@ -63,18 +66,18 @@ export function loadEnvironmentVariables() {
|
|
|
63
66
|
assertIsAddress(destinationAddress);
|
|
64
67
|
assertIsAddress(koraAddress);
|
|
65
68
|
return {
|
|
66
|
-
|
|
69
|
+
commitment,
|
|
70
|
+
destinationAddress,
|
|
67
71
|
koraAddress,
|
|
72
|
+
koraRpcUrl,
|
|
68
73
|
koraSignerType,
|
|
69
|
-
commitment,
|
|
70
|
-
tokenDecimals,
|
|
71
|
-
tokenDropAmount,
|
|
72
74
|
solDropAmount,
|
|
73
75
|
solanaRpcUrl,
|
|
74
76
|
solanaWsUrl,
|
|
75
|
-
testWalletSecret,
|
|
76
77
|
testUsdcMintSecret,
|
|
77
|
-
|
|
78
|
+
testWalletSecret,
|
|
79
|
+
tokenDecimals,
|
|
80
|
+
tokenDropAmount,
|
|
78
81
|
};
|
|
79
82
|
}
|
|
80
83
|
async function createKeyPairSigners() {
|
|
@@ -82,9 +85,9 @@ async function createKeyPairSigners() {
|
|
|
82
85
|
const testWallet = await createKeyPairSignerFromB58Secret(testWalletSecret);
|
|
83
86
|
const usdcMint = await createKeyPairSignerFromB58Secret(testUsdcMintSecret);
|
|
84
87
|
return {
|
|
88
|
+
destinationAddress,
|
|
85
89
|
testWallet,
|
|
86
90
|
usdcMint,
|
|
87
|
-
destinationAddress,
|
|
88
91
|
};
|
|
89
92
|
}
|
|
90
93
|
const createDefaultTransaction = async (client, feePayer, computeLimit = MAX_COMPUTE_UNIT_LIMIT, feeMicroLamports = 1n) => {
|
|
@@ -131,46 +134,46 @@ async function initializeToken({ client, mintAuthority, payer, owner, mint, drop
|
|
|
131
134
|
const baseInstructions = [
|
|
132
135
|
// Create the Mint Account
|
|
133
136
|
getCreateAccountInstruction({
|
|
134
|
-
payer,
|
|
135
|
-
newAccount: mint,
|
|
136
137
|
lamports: mintRent,
|
|
137
|
-
|
|
138
|
+
newAccount: mint,
|
|
139
|
+
payer,
|
|
138
140
|
programAddress: TOKEN_PROGRAM_ADDRESS,
|
|
141
|
+
space: mintSpace,
|
|
139
142
|
}),
|
|
140
143
|
// Initialize the Mint
|
|
141
144
|
getInitializeMintInstruction({
|
|
142
|
-
mint: mint.address,
|
|
143
145
|
decimals,
|
|
146
|
+
mint: mint.address,
|
|
144
147
|
mintAuthority: mintAuthority.address,
|
|
145
148
|
}),
|
|
146
149
|
// Create Associated Token Account
|
|
147
150
|
await getCreateAssociatedTokenIdempotentInstructionAsync({
|
|
148
151
|
mint: mint.address,
|
|
149
|
-
payer,
|
|
150
152
|
owner: owner.address,
|
|
153
|
+
payer,
|
|
151
154
|
}),
|
|
152
155
|
// Mint To the Destination Associated Token Account
|
|
153
156
|
getMintToInstruction({
|
|
154
|
-
mint: mint.address,
|
|
155
|
-
token: ata,
|
|
156
157
|
amount: BigInt(dropAmount * 10 ** decimals),
|
|
158
|
+
mint: mint.address,
|
|
157
159
|
mintAuthority,
|
|
160
|
+
token: ata,
|
|
158
161
|
}),
|
|
159
162
|
];
|
|
160
163
|
// Generate Create ATA instructions for other token accounts we wish to add
|
|
161
164
|
const otherAtaInstructions = otherAtaWallets
|
|
162
165
|
? await Promise.all(otherAtaWallets.map(async (wallet) => await getCreateAssociatedTokenIdempotentInstructionAsync({
|
|
163
166
|
mint: mint.address,
|
|
164
|
-
payer,
|
|
165
167
|
owner: wallet,
|
|
168
|
+
payer,
|
|
166
169
|
})))
|
|
167
170
|
: [];
|
|
168
171
|
const alreadyExists = await mintExists(client, mint.address);
|
|
169
|
-
|
|
172
|
+
const instructions = alreadyExists ? [...otherAtaInstructions] : [...baseInstructions, ...otherAtaInstructions];
|
|
170
173
|
await sendAndConfirmInstructions(client, payer, instructions, 'Initialize token and ATAs', 'finalized');
|
|
171
174
|
}
|
|
172
175
|
async function setupTestSuite() {
|
|
173
|
-
const { koraAddress, koraRpcUrl,
|
|
176
|
+
const { koraAddress, koraRpcUrl, tokenDecimals, tokenDropAmount, solDropAmount, solanaRpcUrl, solanaWsUrl } = loadEnvironmentVariables();
|
|
174
177
|
// Load auth config from environment if not provided
|
|
175
178
|
const authConfig = process.env.ENABLE_AUTH === 'true'
|
|
176
179
|
? {
|
|
@@ -202,21 +205,21 @@ async function setupTestSuite() {
|
|
|
202
205
|
// Initialize token and ATAs
|
|
203
206
|
await initializeToken({
|
|
204
207
|
client,
|
|
205
|
-
mintAuthority,
|
|
206
|
-
payer: mintAuthority,
|
|
207
|
-
owner: testWallet,
|
|
208
|
-
mint: usdcMint,
|
|
209
|
-
dropAmount: tokenDropAmount,
|
|
210
208
|
decimals: tokenDecimals,
|
|
209
|
+
dropAmount: tokenDropAmount,
|
|
210
|
+
mint: usdcMint,
|
|
211
|
+
mintAuthority,
|
|
211
212
|
otherAtaWallets: [testWallet.address, koraAddress, destinationAddress],
|
|
213
|
+
owner: testWallet,
|
|
214
|
+
payer: mintAuthority,
|
|
212
215
|
});
|
|
213
216
|
return {
|
|
217
|
+
destinationAddress,
|
|
218
|
+
koraAddress,
|
|
214
219
|
koraClient: new KoraClient({ rpcUrl: koraRpcUrl, ...authConfig }),
|
|
215
220
|
koraRpcUrl,
|
|
216
221
|
testWallet,
|
|
217
222
|
usdcMint: usdcMint.address,
|
|
218
|
-
destinationAddress,
|
|
219
|
-
koraAddress,
|
|
220
223
|
};
|
|
221
224
|
}
|
|
222
225
|
const mintExists = async (client, mint) => {
|
|
@@ -224,7 +227,7 @@ const mintExists = async (client, mint) => {
|
|
|
224
227
|
const mintAccount = await client.rpc.getAccountInfo(mint).send();
|
|
225
228
|
return mintAccount.value !== null;
|
|
226
229
|
}
|
|
227
|
-
catch
|
|
230
|
+
catch {
|
|
228
231
|
return false;
|
|
229
232
|
}
|
|
230
233
|
};
|