@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
package/dist/test/auth-setup.js
CHANGED
|
@@ -5,34 +5,34 @@ export function runAuthenticationTests() {
|
|
|
5
5
|
describe('Authentication', () => {
|
|
6
6
|
it('should fail with incorrect API key', async () => {
|
|
7
7
|
const client = new KoraClient({
|
|
8
|
-
rpcUrl: koraRpcUrl,
|
|
9
8
|
apiKey: 'WRONG-API-KEY',
|
|
9
|
+
rpcUrl: koraRpcUrl,
|
|
10
10
|
});
|
|
11
11
|
// Auth failure should result in an error (empty response body causes JSON parse error)
|
|
12
12
|
await expect(client.getConfig()).rejects.toThrow();
|
|
13
13
|
});
|
|
14
14
|
it('should fail with incorrect HMAC secret', async () => {
|
|
15
15
|
const client = new KoraClient({
|
|
16
|
-
rpcUrl: koraRpcUrl,
|
|
17
16
|
hmacSecret: 'WRONG-HMAC-SECRET',
|
|
17
|
+
rpcUrl: koraRpcUrl,
|
|
18
18
|
});
|
|
19
19
|
// Auth failure should result in an error
|
|
20
20
|
await expect(client.getConfig()).rejects.toThrow();
|
|
21
21
|
});
|
|
22
22
|
it('should fail with both incorrect credentials', async () => {
|
|
23
23
|
const client = new KoraClient({
|
|
24
|
-
rpcUrl: koraRpcUrl,
|
|
25
24
|
apiKey: 'WRONG-API-KEY',
|
|
26
25
|
hmacSecret: 'WRONG-HMAC-SECRET',
|
|
26
|
+
rpcUrl: koraRpcUrl,
|
|
27
27
|
});
|
|
28
28
|
// Auth failure should result in an error
|
|
29
29
|
await expect(client.getConfig()).rejects.toThrow();
|
|
30
30
|
});
|
|
31
31
|
it('should succeed with correct credentials', async () => {
|
|
32
32
|
const client = new KoraClient({
|
|
33
|
-
rpcUrl: koraRpcUrl,
|
|
34
33
|
apiKey: 'test-api-key-123',
|
|
35
34
|
hmacSecret: 'test-hmac-secret-456',
|
|
35
|
+
rpcUrl: koraRpcUrl,
|
|
36
36
|
});
|
|
37
37
|
const config = await client.getConfig();
|
|
38
38
|
expect(config).toBeDefined();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { appendTransactionMessageInstruction, compileTransaction, createTransactionMessage, getBase64Decoder, getBase64EncodedWireTransaction, getBase64Encoder, getTransactionDecoder, getTransactionEncoder, partiallySignTransaction, pipe, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, } from '@solana/kit';
|
|
2
|
+
import { findAssociatedTokenPda, getTransferInstruction, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
|
|
2
3
|
import { runAuthenticationTests } from './auth-setup.js';
|
|
3
|
-
import
|
|
4
|
-
import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
|
|
4
|
+
import setupTestSuite from './setup.js';
|
|
5
5
|
function transactionFromBase64(base64) {
|
|
6
6
|
const encoder = getBase64Encoder();
|
|
7
7
|
const decoder = getTransactionDecoder();
|
|
@@ -14,6 +14,45 @@ function transactionToBase64(transaction) {
|
|
|
14
14
|
const base64Decoder = getBase64Decoder();
|
|
15
15
|
return base64Decoder.decode(txBytes);
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Helper to build a SPL token transfer transaction.
|
|
19
|
+
* This replaces the deprecated transferTransaction endpoint.
|
|
20
|
+
*/
|
|
21
|
+
async function buildTokenTransferTransaction(params) {
|
|
22
|
+
const { client, amount, mint, sourceWallet, destinationWallet } = params;
|
|
23
|
+
// Get the payer signer from Kora (fee payer)
|
|
24
|
+
const { signer_address } = await client.getPayerSigner();
|
|
25
|
+
// Get blockhash
|
|
26
|
+
const { blockhash } = await client.getBlockhash();
|
|
27
|
+
// Find source and destination ATAs
|
|
28
|
+
const [sourceAta] = await findAssociatedTokenPda({
|
|
29
|
+
mint,
|
|
30
|
+
owner: sourceWallet.address,
|
|
31
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
32
|
+
});
|
|
33
|
+
const [destinationAta] = await findAssociatedTokenPda({
|
|
34
|
+
mint,
|
|
35
|
+
owner: destinationWallet,
|
|
36
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
37
|
+
});
|
|
38
|
+
// Build transfer instruction
|
|
39
|
+
const transferIx = getTransferInstruction({
|
|
40
|
+
amount,
|
|
41
|
+
authority: sourceWallet,
|
|
42
|
+
destination: destinationAta,
|
|
43
|
+
source: sourceAta,
|
|
44
|
+
});
|
|
45
|
+
// Build transaction message with Kora as fee payer
|
|
46
|
+
// We create a mock signer for the fee payer address since we only need the address
|
|
47
|
+
const feePayerSigner = {
|
|
48
|
+
address: signer_address,
|
|
49
|
+
};
|
|
50
|
+
const transactionMessage = pipe(createTransactionMessage({ version: 0 }), tx => setTransactionMessageFeePayerSigner(feePayerSigner, tx), tx => setTransactionMessageLifetimeUsingBlockhash({ blockhash: blockhash, lastValidBlockHeight: BigInt(Number.MAX_SAFE_INTEGER) }, tx), tx => appendTransactionMessageInstruction(transferIx, tx));
|
|
51
|
+
// Compile to transaction
|
|
52
|
+
const transaction = compileTransaction(transactionMessage);
|
|
53
|
+
const base64Transaction = getBase64EncodedWireTransaction(transaction);
|
|
54
|
+
return { blockhash: blockhash, transaction: base64Transaction };
|
|
55
|
+
}
|
|
17
56
|
const AUTH_ENABLED = process.env.ENABLE_AUTH === 'true';
|
|
18
57
|
const KORA_SIGNER_TYPE = process.env.KORA_SIGNER_TYPE || 'memory';
|
|
19
58
|
describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without auth'} | signer type: ${KORA_SIGNER_TYPE})`, () => {
|
|
@@ -90,7 +129,6 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
|
|
|
90
129
|
expect(config.enabled_methods.get_supported_tokens).toBeDefined();
|
|
91
130
|
expect(config.enabled_methods.sign_transaction).toBeDefined();
|
|
92
131
|
expect(config.enabled_methods.sign_and_send_transaction).toBeDefined();
|
|
93
|
-
expect(config.enabled_methods.transfer_transaction).toBeDefined();
|
|
94
132
|
expect(config.enabled_methods.get_blockhash).toBeDefined();
|
|
95
133
|
expect(config.enabled_methods.get_config).toBeDefined();
|
|
96
134
|
expect(config.enabled_methods.get_version).toBeDefined();
|
|
@@ -123,30 +161,15 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
|
|
|
123
161
|
});
|
|
124
162
|
});
|
|
125
163
|
describe('Transaction Operations', () => {
|
|
126
|
-
it('should create transfer transaction (DEPRECATED endpoint)', async () => {
|
|
127
|
-
const request = {
|
|
128
|
-
amount: 1000000, // 1 USDC
|
|
129
|
-
token: usdcMint,
|
|
130
|
-
source: testWalletAddress,
|
|
131
|
-
destination: koraAddress, // user specifies destination
|
|
132
|
-
};
|
|
133
|
-
const response = await client.transferTransaction(request);
|
|
134
|
-
expect(response).toBeDefined();
|
|
135
|
-
expect(response.transaction).toBeDefined();
|
|
136
|
-
expect(response.blockhash).toBeDefined();
|
|
137
|
-
expect(response.message).toBeDefined();
|
|
138
|
-
expect(response.signer_pubkey).toBeDefined();
|
|
139
|
-
});
|
|
140
164
|
it('should estimate transaction fee', async () => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
};
|
|
148
|
-
const
|
|
149
|
-
const fee = await client.estimateTransactionFee({ transaction, fee_token: usdcMint });
|
|
165
|
+
const { transaction } = await buildTokenTransferTransaction({
|
|
166
|
+
amount: 1000000n,
|
|
167
|
+
client,
|
|
168
|
+
destinationWallet: koraAddress,
|
|
169
|
+
mint: usdcMint,
|
|
170
|
+
sourceWallet: testWallet,
|
|
171
|
+
});
|
|
172
|
+
const fee = await client.estimateTransactionFee({ fee_token: usdcMint, transaction });
|
|
150
173
|
expect(fee).toBeDefined();
|
|
151
174
|
expect(typeof fee.fee_in_lamports).toBe('number');
|
|
152
175
|
expect(fee.fee_in_lamports).toBeGreaterThan(0);
|
|
@@ -154,13 +177,13 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
|
|
|
154
177
|
expect(fee.fee_in_token).toBeGreaterThan(0);
|
|
155
178
|
});
|
|
156
179
|
it('should sign transaction', async () => {
|
|
157
|
-
const
|
|
158
|
-
amount:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
180
|
+
const { transaction } = await buildTokenTransferTransaction({
|
|
181
|
+
amount: 1000000n,
|
|
182
|
+
client,
|
|
183
|
+
destinationWallet: koraAddress,
|
|
184
|
+
mint: usdcMint,
|
|
185
|
+
sourceWallet: testWallet,
|
|
186
|
+
});
|
|
164
187
|
const signResult = await client.signTransaction({
|
|
165
188
|
transaction,
|
|
166
189
|
});
|
|
@@ -168,13 +191,13 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
|
|
|
168
191
|
expect(signResult.signed_transaction).toBeDefined();
|
|
169
192
|
});
|
|
170
193
|
it('should sign and send transaction', async () => {
|
|
171
|
-
const
|
|
172
|
-
amount:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
194
|
+
const { transaction: transactionString } = await buildTokenTransferTransaction({
|
|
195
|
+
amount: 1000000n,
|
|
196
|
+
client,
|
|
197
|
+
destinationWallet: koraAddress,
|
|
198
|
+
mint: usdcMint,
|
|
199
|
+
sourceWallet: testWallet,
|
|
200
|
+
});
|
|
178
201
|
const transaction = transactionFromBase64(transactionString);
|
|
179
202
|
// Partially sign transaction with test wallet before sending
|
|
180
203
|
// Kora will add fee payer signature via signAndSendTransaction
|
|
@@ -185,29 +208,30 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
|
|
|
185
208
|
});
|
|
186
209
|
expect(signResult).toBeDefined();
|
|
187
210
|
expect(signResult.signed_transaction).toBeDefined();
|
|
211
|
+
expect(signResult.signature).toBeDefined();
|
|
188
212
|
});
|
|
189
213
|
it('should get payment instruction', async () => {
|
|
190
|
-
const
|
|
191
|
-
amount:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
214
|
+
const { transaction } = await buildTokenTransferTransaction({
|
|
215
|
+
amount: 1000000n,
|
|
216
|
+
client,
|
|
217
|
+
destinationWallet: koraAddress,
|
|
218
|
+
mint: usdcMint,
|
|
219
|
+
sourceWallet: testWallet,
|
|
220
|
+
});
|
|
196
221
|
const [expectedSenderAta] = await findAssociatedTokenPda({
|
|
222
|
+
mint: usdcMint,
|
|
197
223
|
owner: testWalletAddress,
|
|
198
224
|
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
199
|
-
mint: usdcMint,
|
|
200
225
|
});
|
|
201
226
|
const [koraAta] = await findAssociatedTokenPda({
|
|
227
|
+
mint: usdcMint,
|
|
202
228
|
owner: koraAddress,
|
|
203
229
|
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
204
|
-
mint: usdcMint,
|
|
205
230
|
});
|
|
206
|
-
const {
|
|
207
|
-
const { payment_instruction, payment_amount, payment_token, payment_address, signer_address, original_transaction, } = await client.getPaymentInstruction({
|
|
208
|
-
transaction,
|
|
231
|
+
const { payment_instruction, payment_amount: _payment_amount, payment_token, payment_address, signer_address, original_transaction, } = await client.getPaymentInstruction({
|
|
209
232
|
fee_token: usdcMint,
|
|
210
233
|
source_wallet: testWalletAddress,
|
|
234
|
+
transaction,
|
|
211
235
|
});
|
|
212
236
|
expect(payment_instruction).toBeDefined();
|
|
213
237
|
expect(payment_instruction.programAddress).toBe(TOKEN_PROGRAM_ADDRESS);
|
|
@@ -225,20 +249,20 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
|
|
|
225
249
|
describe('Bundle Operations', () => {
|
|
226
250
|
it('should sign bundle of transactions', async () => {
|
|
227
251
|
// Create two transfer transactions for the bundle
|
|
228
|
-
const
|
|
229
|
-
amount:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
252
|
+
const { transaction: tx1String } = await buildTokenTransferTransaction({
|
|
253
|
+
amount: 1000000n,
|
|
254
|
+
client,
|
|
255
|
+
destinationWallet: koraAddress,
|
|
256
|
+
mint: usdcMint,
|
|
257
|
+
sourceWallet: testWallet,
|
|
258
|
+
});
|
|
259
|
+
const { transaction: tx2String } = await buildTokenTransferTransaction({
|
|
260
|
+
amount: 500000n,
|
|
261
|
+
client,
|
|
262
|
+
destinationWallet: koraAddress,
|
|
263
|
+
mint: usdcMint,
|
|
264
|
+
sourceWallet: testWallet,
|
|
265
|
+
});
|
|
242
266
|
// Partially sign both transactions with test wallet
|
|
243
267
|
const tx1 = transactionFromBase64(tx1String);
|
|
244
268
|
const tx2 = transactionFromBase64(tx2String);
|
|
@@ -257,20 +281,20 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
|
|
|
257
281
|
});
|
|
258
282
|
it('should sign and send bundle of transactions', async () => {
|
|
259
283
|
// Create two transfer transactions for the bundle
|
|
260
|
-
const
|
|
261
|
-
amount:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
284
|
+
const { transaction: tx1String } = await buildTokenTransferTransaction({
|
|
285
|
+
amount: 1000000n,
|
|
286
|
+
client,
|
|
287
|
+
destinationWallet: koraAddress,
|
|
288
|
+
mint: usdcMint,
|
|
289
|
+
sourceWallet: testWallet,
|
|
290
|
+
});
|
|
291
|
+
const { transaction: tx2String } = await buildTokenTransferTransaction({
|
|
292
|
+
amount: 500000n,
|
|
293
|
+
client,
|
|
294
|
+
destinationWallet: koraAddress,
|
|
295
|
+
mint: usdcMint,
|
|
296
|
+
sourceWallet: testWallet,
|
|
297
|
+
});
|
|
274
298
|
// Partially sign both transactions with test wallet
|
|
275
299
|
const tx1 = transactionFromBase64(tx1String);
|
|
276
300
|
const tx2 = transactionFromBase64(tx2String);
|
|
@@ -291,69 +315,26 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
|
|
|
291
315
|
});
|
|
292
316
|
});
|
|
293
317
|
describe('Error Handling', () => {
|
|
294
|
-
it('should handle invalid token address', async () => {
|
|
295
|
-
const request = {
|
|
296
|
-
amount: 1000000,
|
|
297
|
-
token: 'InvalidTokenAddress',
|
|
298
|
-
source: testWalletAddress,
|
|
299
|
-
destination: koraAddress,
|
|
300
|
-
};
|
|
301
|
-
await expect(client.transferTransaction(request)).rejects.toThrow();
|
|
302
|
-
});
|
|
303
|
-
it('should handle invalid amount', async () => {
|
|
304
|
-
const request = {
|
|
305
|
-
amount: -1, // Invalid amount
|
|
306
|
-
token: usdcMint,
|
|
307
|
-
source: testWalletAddress,
|
|
308
|
-
destination: koraAddress,
|
|
309
|
-
};
|
|
310
|
-
await expect(client.transferTransaction(request)).rejects.toThrow();
|
|
311
|
-
});
|
|
312
318
|
it('should handle invalid transaction for signing', async () => {
|
|
313
319
|
await expect(client.signTransaction({
|
|
314
320
|
transaction: 'invalid_transaction',
|
|
315
321
|
})).rejects.toThrow();
|
|
316
322
|
});
|
|
317
323
|
it('should handle invalid transaction for fee estimation', async () => {
|
|
318
|
-
await expect(client.estimateTransactionFee({ transaction: 'invalid_transaction'
|
|
319
|
-
});
|
|
320
|
-
it('should handle non-allowed token for fee payment', async () => {
|
|
321
|
-
const transferRequest = {
|
|
322
|
-
amount: 1000000,
|
|
323
|
-
token: usdcMint,
|
|
324
|
-
source: testWalletAddress,
|
|
325
|
-
destination: koraAddress,
|
|
326
|
-
};
|
|
327
|
-
// TODO: API has an error. this endpoint should verify the provided fee token is supported
|
|
328
|
-
const { transaction } = await client.transferTransaction(transferRequest);
|
|
329
|
-
const fee = await client.estimateTransactionFee({ transaction, fee_token: usdcMint });
|
|
330
|
-
expect(fee).toBeDefined();
|
|
331
|
-
expect(typeof fee.fee_in_lamports).toBe('number');
|
|
332
|
-
expect(fee.fee_in_lamports).toBeGreaterThan(0);
|
|
324
|
+
await expect(client.estimateTransactionFee({ fee_token: usdcMint, transaction: 'invalid_transaction' })).rejects.toThrow();
|
|
333
325
|
});
|
|
334
326
|
});
|
|
335
327
|
describe('End-to-End Flows', () => {
|
|
336
328
|
it('should handle transfer and sign flow', async () => {
|
|
337
|
-
const
|
|
338
|
-
amount:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const { transaction } = await client.transferTransaction(request);
|
|
329
|
+
const { transaction } = await buildTokenTransferTransaction({
|
|
330
|
+
amount: 1000000n,
|
|
331
|
+
client,
|
|
332
|
+
destinationWallet: koraAddress,
|
|
333
|
+
mint: usdcMint,
|
|
334
|
+
sourceWallet: testWallet,
|
|
335
|
+
});
|
|
345
336
|
const signResult = await client.signTransaction({ transaction });
|
|
346
337
|
expect(signResult.signed_transaction).toBeDefined();
|
|
347
338
|
});
|
|
348
|
-
it('should reject transaction with non-allowed token', async () => {
|
|
349
|
-
const invalidTokenMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; // Mainnet USDC mint
|
|
350
|
-
const request = {
|
|
351
|
-
amount: 1000000,
|
|
352
|
-
token: invalidTokenMint,
|
|
353
|
-
source: testWalletAddress,
|
|
354
|
-
destination: koraAddress,
|
|
355
|
-
};
|
|
356
|
-
await expect(client.transferTransaction(request)).rejects.toThrow();
|
|
357
|
-
});
|
|
358
339
|
});
|
|
359
340
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|