@solana/kora 0.2.0-beta.0 → 0.2.0-beta.2

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,4 +1,4 @@
1
- import { Config, EstimateTransactionFeeRequest, EstimateTransactionFeeResponse, GetBlockhashResponse, GetSupportedTokensResponse, SignAndSendTransactionRequest, SignAndSendTransactionResponse, SignTransactionRequest, SignTransactionResponse, TransferTransactionRequest, TransferTransactionResponse, KoraClientOptions, GetPayerSignerResponse, GetPaymentInstructionRequest, GetPaymentInstructionResponse } from './types/index.js';
1
+ import { Config, EstimateTransactionFeeRequest, EstimateTransactionFeeResponse, EstimateBundleFeeRequest, EstimateBundleFeeResponse, GetBlockhashResponse, GetSupportedTokensResponse, GetVersionResponse, SignAndSendTransactionRequest, SignAndSendTransactionResponse, SignTransactionRequest, SignTransactionResponse, SignBundleRequest, SignBundleResponse, SignAndSendBundleRequest, SignAndSendBundleResponse, TransferTransactionRequest, TransferTransactionResponse, KoraClientOptions, GetPayerSignerResponse, GetPaymentInstructionRequest, GetPaymentInstructionResponse } from './types/index.js';
2
2
  /**
3
3
  * Kora RPC client for interacting with the Kora paymaster service.
4
4
  *
@@ -67,6 +67,18 @@ export declare class KoraClient {
67
67
  * ```
68
68
  */
69
69
  getBlockhash(): Promise<GetBlockhashResponse>;
70
+ /**
71
+ * Gets the version of the Kora server.
72
+ * @returns Object containing the server version
73
+ * @throws {Error} When the RPC call fails
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const { version } = await client.getVersion();
78
+ * console.log('Server version:', version);
79
+ * ```
80
+ */
81
+ getVersion(): Promise<GetVersionResponse>;
70
82
  /**
71
83
  * Retrieves the list of tokens supported for fee payment.
72
84
  * @returns Object containing an array of supported token mint addresses
@@ -99,6 +111,25 @@ export declare class KoraClient {
99
111
  * ```
100
112
  */
101
113
  estimateTransactionFee(request: EstimateTransactionFeeRequest): Promise<EstimateTransactionFeeResponse>;
114
+ /**
115
+ * Estimates the bundle fee in both lamports and the specified token.
116
+ * @param request - Bundle fee estimation request parameters
117
+ * @param request.transactions - Array of base64-encoded transactions to estimate fees for
118
+ * @param request.fee_token - Mint address of the token to calculate fees in
119
+ * @returns Total fee amounts across all transactions in both lamports and the specified token
120
+ * @throws {Error} When the RPC call fails, the bundle is invalid, or the token is not supported
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const fees = await client.estimateBundleFee({
125
+ * transactions: ['base64EncodedTransaction1', 'base64EncodedTransaction2'],
126
+ * fee_token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC
127
+ * });
128
+ * console.log('Total fee in lamports:', fees.fee_in_lamports);
129
+ * console.log('Total fee in USDC:', fees.fee_in_token);
130
+ * ```
131
+ */
132
+ estimateBundleFee(request: EstimateBundleFeeRequest): Promise<EstimateBundleFeeResponse>;
102
133
  /**
103
134
  * Signs a transaction with the Kora fee payer without broadcasting it.
104
135
  * @param request - Sign request parameters
@@ -133,13 +164,55 @@ export declare class KoraClient {
133
164
  */
134
165
  signAndSendTransaction(request: SignAndSendTransactionRequest): Promise<SignAndSendTransactionResponse>;
135
166
  /**
136
- * Creates a token transfer transaction with Kora as the fee payer.
167
+ * Signs a bundle of transactions with the Kora fee payer without broadcasting.
168
+ * @param request - Sign bundle request parameters
169
+ * @param request.transactions - Array of base64-encoded transactions to sign
170
+ * @param request.signer_key - Optional signer address for the transactions
171
+ * @param request.sig_verify - Optional signature verification (defaults to false)
172
+ * @returns Array of signed transactions and signer public key
173
+ * @throws {Error} When the RPC call fails or validation fails
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const result = await client.signBundle({
178
+ * transactions: ['base64Tx1', 'base64Tx2']
179
+ * });
180
+ * console.log('Signed transactions:', result.signed_transactions);
181
+ * console.log('Signer:', result.signer_pubkey);
182
+ * ```
183
+ */
184
+ signBundle(request: SignBundleRequest): Promise<SignBundleResponse>;
185
+ /**
186
+ * Signs a bundle of transactions and sends them to Jito block engine.
187
+ * @param request - Sign and send bundle request parameters
188
+ * @param request.transactions - Array of base64-encoded transactions to sign and send
189
+ * @param request.signer_key - Optional signer address for the transactions
190
+ * @param request.sig_verify - Optional signature verification (defaults to false)
191
+ * @returns Array of signed transactions, signer public key, and Jito bundle UUID
192
+ * @throws {Error} When the RPC call fails, validation fails, or Jito submission fails
193
+ *
194
+ * @example
195
+ * ```typescript
196
+ * const result = await client.signAndSendBundle({
197
+ * transactions: ['base64Tx1', 'base64Tx2']
198
+ * });
199
+ * console.log('Bundle UUID:', result.bundle_uuid);
200
+ * console.log('Signed transactions:', result.signed_transactions);
201
+ * ```
202
+ */
203
+ signAndSendBundle(request: SignAndSendBundleRequest): Promise<SignAndSendBundleResponse>;
204
+ /**
205
+ * Creates an unsigned transfer transaction.
206
+ *
207
+ * @deprecated Use `getPaymentInstruction` instead for fee payment flows.
208
+ *
137
209
  * @param request - Transfer request parameters
138
210
  * @param request.amount - Amount to transfer (in token's smallest unit)
139
211
  * @param request.token - Mint address of the token to transfer
140
212
  * @param request.source - Source wallet public key
141
213
  * @param request.destination - Destination wallet public key
142
- * @returns Base64-encoded signed transaction, base64-encoded message, blockhash, and parsed instructions
214
+ * @param request.signer_key - Optional signer key to select specific Kora signer
215
+ * @returns Unsigned transaction, message, blockhash, and signer info
143
216
  * @throws {Error} When the RPC call fails or token is not supported
144
217
  *
145
218
  * @example
@@ -148,11 +221,9 @@ export declare class KoraClient {
148
221
  * amount: 1000000, // 1 USDC (6 decimals)
149
222
  * token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
150
223
  * source: 'sourceWalletPublicKey',
151
- * destination: 'destinationWalletPublicKey'
224
+ * destination: 'destinationWalletPublicKey',
152
225
  * });
153
- * console.log('Transaction:', transfer.transaction);
154
- * console.log('Message:', transfer.message);
155
- * console.log('Instructions:', transfer.instructions);
226
+ * console.log('Signer:', transfer.signer_pubkey);
156
227
  * ```
157
228
  */
158
229
  transferTransaction(request: TransferTransactionRequest): Promise<TransferTransactionResponse>;
@@ -1,7 +1,7 @@
1
1
  import { assertIsAddress, createNoopSigner } from '@solana/kit';
2
2
  import crypto from 'crypto';
3
- import { getInstructionsFromBase64Message } from './utils/transaction.js';
4
3
  import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS, getTransferInstruction } from '@solana-program/token';
4
+ import { getInstructionsFromBase64Message } from './utils/transaction.js';
5
5
  /**
6
6
  * Kora RPC client for interacting with the Kora paymaster service.
7
7
  *
@@ -117,6 +117,20 @@ export class KoraClient {
117
117
  async getBlockhash() {
118
118
  return this.rpcRequest('getBlockhash', undefined);
119
119
  }
120
+ /**
121
+ * Gets the version of the Kora server.
122
+ * @returns Object containing the server version
123
+ * @throws {Error} When the RPC call fails
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const { version } = await client.getVersion();
128
+ * console.log('Server version:', version);
129
+ * ```
130
+ */
131
+ async getVersion() {
132
+ return this.rpcRequest('getVersion', undefined);
133
+ }
120
134
  /**
121
135
  * Retrieves the list of tokens supported for fee payment.
122
136
  * @returns Object containing an array of supported token mint addresses
@@ -153,6 +167,27 @@ export class KoraClient {
153
167
  async estimateTransactionFee(request) {
154
168
  return this.rpcRequest('estimateTransactionFee', request);
155
169
  }
170
+ /**
171
+ * Estimates the bundle fee in both lamports and the specified token.
172
+ * @param request - Bundle fee estimation request parameters
173
+ * @param request.transactions - Array of base64-encoded transactions to estimate fees for
174
+ * @param request.fee_token - Mint address of the token to calculate fees in
175
+ * @returns Total fee amounts across all transactions in both lamports and the specified token
176
+ * @throws {Error} When the RPC call fails, the bundle is invalid, or the token is not supported
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * const fees = await client.estimateBundleFee({
181
+ * transactions: ['base64EncodedTransaction1', 'base64EncodedTransaction2'],
182
+ * fee_token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC
183
+ * });
184
+ * console.log('Total fee in lamports:', fees.fee_in_lamports);
185
+ * console.log('Total fee in USDC:', fees.fee_in_token);
186
+ * ```
187
+ */
188
+ async estimateBundleFee(request) {
189
+ return this.rpcRequest('estimateBundleFee', request);
190
+ }
156
191
  /**
157
192
  * Signs a transaction with the Kora fee payer without broadcasting it.
158
193
  * @param request - Sign request parameters
@@ -191,13 +226,59 @@ export class KoraClient {
191
226
  return this.rpcRequest('signAndSendTransaction', request);
192
227
  }
193
228
  /**
194
- * Creates a token transfer transaction with Kora as the fee payer.
229
+ * Signs a bundle of transactions with the Kora fee payer without broadcasting.
230
+ * @param request - Sign bundle request parameters
231
+ * @param request.transactions - Array of base64-encoded transactions to sign
232
+ * @param request.signer_key - Optional signer address for the transactions
233
+ * @param request.sig_verify - Optional signature verification (defaults to false)
234
+ * @returns Array of signed transactions and signer public key
235
+ * @throws {Error} When the RPC call fails or validation fails
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * const result = await client.signBundle({
240
+ * transactions: ['base64Tx1', 'base64Tx2']
241
+ * });
242
+ * console.log('Signed transactions:', result.signed_transactions);
243
+ * console.log('Signer:', result.signer_pubkey);
244
+ * ```
245
+ */
246
+ async signBundle(request) {
247
+ return this.rpcRequest('signBundle', request);
248
+ }
249
+ /**
250
+ * Signs a bundle of transactions and sends them to Jito block engine.
251
+ * @param request - Sign and send bundle request parameters
252
+ * @param request.transactions - Array of base64-encoded transactions to sign and send
253
+ * @param request.signer_key - Optional signer address for the transactions
254
+ * @param request.sig_verify - Optional signature verification (defaults to false)
255
+ * @returns Array of signed transactions, signer public key, and Jito bundle UUID
256
+ * @throws {Error} When the RPC call fails, validation fails, or Jito submission fails
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * const result = await client.signAndSendBundle({
261
+ * transactions: ['base64Tx1', 'base64Tx2']
262
+ * });
263
+ * console.log('Bundle UUID:', result.bundle_uuid);
264
+ * console.log('Signed transactions:', result.signed_transactions);
265
+ * ```
266
+ */
267
+ async signAndSendBundle(request) {
268
+ return this.rpcRequest('signAndSendBundle', request);
269
+ }
270
+ /**
271
+ * Creates an unsigned transfer transaction.
272
+ *
273
+ * @deprecated Use `getPaymentInstruction` instead for fee payment flows.
274
+ *
195
275
  * @param request - Transfer request parameters
196
276
  * @param request.amount - Amount to transfer (in token's smallest unit)
197
277
  * @param request.token - Mint address of the token to transfer
198
278
  * @param request.source - Source wallet public key
199
279
  * @param request.destination - Destination wallet public key
200
- * @returns Base64-encoded signed transaction, base64-encoded message, blockhash, and parsed instructions
280
+ * @param request.signer_key - Optional signer key to select specific Kora signer
281
+ * @returns Unsigned transaction, message, blockhash, and signer info
201
282
  * @throws {Error} When the RPC call fails or token is not supported
202
283
  *
203
284
  * @example
@@ -206,11 +287,9 @@ export class KoraClient {
206
287
  * amount: 1000000, // 1 USDC (6 decimals)
207
288
  * token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
208
289
  * source: 'sourceWalletPublicKey',
209
- * destination: 'destinationWalletPublicKey'
290
+ * destination: 'destinationWalletPublicKey',
210
291
  * });
211
- * console.log('Transaction:', transfer.transaction);
212
- * console.log('Message:', transfer.message);
213
- * console.log('Instructions:', transfer.instructions);
292
+ * console.log('Signer:', transfer.signer_pubkey);
214
293
  * ```
215
294
  */
216
295
  async transferTransaction(request) {
@@ -268,6 +347,9 @@ export class KoraClient {
268
347
  tokenProgram: token_program_id,
269
348
  mint: fee_token,
270
349
  });
350
+ if (fee_in_token === undefined) {
351
+ throw new Error('Fee token was specified but fee_in_token was not returned from server');
352
+ }
271
353
  const paymentInstruction = getTransferInstruction({
272
354
  source: sourceTokenAccount,
273
355
  destination: destinationTokenAccount,
@@ -3,7 +3,8 @@ import { Instruction } from '@solana/kit';
3
3
  * Request Types
4
4
  */
5
5
  /**
6
- * Parameters for creating a token transfer transaction.
6
+ * Parameters for creating a transfer transaction.
7
+ * @deprecated Use `getPaymentInstruction` instead for fee payment flows.
7
8
  */
8
9
  export interface TransferTransactionRequest {
9
10
  /** Amount to transfer in the token's smallest unit (e.g., lamports for SOL) */
@@ -14,7 +15,7 @@ export interface TransferTransactionRequest {
14
15
  source: string;
15
16
  /** Public key of the destination wallet (not token account) */
16
17
  destination: string;
17
- /** Optional signer address for the transaction */
18
+ /** Optional signer key to select a specific Kora signer */
18
19
  signer_key?: string;
19
20
  }
20
21
  /**
@@ -39,6 +40,28 @@ export interface SignAndSendTransactionRequest {
39
40
  /** Optional signer verification during transaction simulation (defaults to false) */
40
41
  sig_verify?: boolean;
41
42
  }
43
+ /**
44
+ * Parameters for signing a bundle of transactions.
45
+ */
46
+ export interface SignBundleRequest {
47
+ /** Array of base64-encoded transactions to sign */
48
+ transactions: string[];
49
+ /** Optional signer address for the transactions */
50
+ signer_key?: string;
51
+ /** Optional signer verification during transaction simulation (defaults to false) */
52
+ sig_verify?: boolean;
53
+ }
54
+ /**
55
+ * Parameters for signing and sending a bundle of transactions via Jito.
56
+ */
57
+ export interface SignAndSendBundleRequest {
58
+ /** Array of base64-encoded transactions to sign and send */
59
+ transactions: string[];
60
+ /** Optional signer address for the transactions */
61
+ signer_key?: string;
62
+ /** Optional signer verification during transaction simulation (defaults to false) */
63
+ sig_verify?: boolean;
64
+ }
42
65
  /**
43
66
  * Parameters for estimating transaction fees.
44
67
  */
@@ -46,12 +69,25 @@ export interface EstimateTransactionFeeRequest {
46
69
  /** Base64-encoded transaction to estimate fees for */
47
70
  transaction: string;
48
71
  /** Mint address of the token to calculate fees in */
49
- fee_token: string;
72
+ fee_token?: string;
50
73
  /** Optional signer address for the transaction */
51
74
  signer_key?: string;
52
75
  /** Optional signer verification during transaction simulation (defaults to false) */
53
76
  sig_verify?: boolean;
54
77
  }
78
+ /**
79
+ * Parameters for estimating bundle fees.
80
+ */
81
+ export interface EstimateBundleFeeRequest {
82
+ /** Array of base64-encoded transactions to estimate fees for */
83
+ transactions: string[];
84
+ /** Mint address of the token to calculate fees in */
85
+ fee_token?: string;
86
+ /** Optional signer address for the transactions */
87
+ signer_key?: string;
88
+ /** Optional signer verification during transaction simulation (defaults to false) */
89
+ sig_verify?: boolean;
90
+ }
55
91
  /**
56
92
  * Parameters for getting a payment instruction.
57
93
  */
@@ -74,15 +110,17 @@ export interface GetPaymentInstructionRequest {
74
110
  */
75
111
  /**
76
112
  * Response from creating a transfer transaction.
113
+ * The transaction is unsigned.
114
+ * @deprecated Use `getPaymentInstruction` instead for fee payment flows.
77
115
  */
78
116
  export interface TransferTransactionResponse {
79
- /** Base64-encoded signed transaction */
117
+ /** Base64-encoded unsigned transaction */
80
118
  transaction: string;
81
- /** Base64-encoded message */
119
+ /** Base64-encoded unsigned message */
82
120
  message: string;
83
121
  /** Recent blockhash used in the transaction */
84
122
  blockhash: string;
85
- /** Public key of the signer used to send the transaction */
123
+ /** Public key of the Kora signer (fee payer) */
86
124
  signer_pubkey: string;
87
125
  /** Parsed instructions from the transaction message */
88
126
  instructions: Instruction[];
@@ -107,6 +145,26 @@ export interface SignAndSendTransactionResponse {
107
145
  /** Public key of the signer used to send the transaction */
108
146
  signer_pubkey: string;
109
147
  }
148
+ /**
149
+ * Response from signing a bundle of transactions.
150
+ */
151
+ export interface SignBundleResponse {
152
+ /** Array of base64-encoded signed transactions */
153
+ signed_transactions: string[];
154
+ /** Public key of the signer used to sign the transactions */
155
+ signer_pubkey: string;
156
+ }
157
+ /**
158
+ * Response from signing and sending a bundle of transactions via Jito.
159
+ */
160
+ export interface SignAndSendBundleResponse {
161
+ /** Array of base64-encoded signed transactions */
162
+ signed_transactions: string[];
163
+ /** Public key of the signer used to sign the transactions */
164
+ signer_pubkey: string;
165
+ /** UUID of the submitted Jito bundle */
166
+ bundle_uuid: string;
167
+ }
110
168
  /**
111
169
  * Response containing the latest blockhash.
112
170
  */
@@ -114,6 +172,13 @@ export interface GetBlockhashResponse {
114
172
  /** Base58-encoded blockhash */
115
173
  blockhash: string;
116
174
  }
175
+ /**
176
+ * Response containing the server version.
177
+ */
178
+ export interface GetVersionResponse {
179
+ /** Server version string */
180
+ version: string;
181
+ }
117
182
  /**
118
183
  * Response containing supported token mint addresses.
119
184
  */
@@ -130,7 +195,22 @@ export interface EstimateTransactionFeeResponse {
130
195
  /**
131
196
  * Transaction fee in the requested token (in decimals value of the token, e.g. 10^6 for USDC)
132
197
  */
133
- fee_in_token: number;
198
+ fee_in_token?: number;
199
+ /** Public key of the signer used to estimate the fee */
200
+ signer_pubkey: string;
201
+ /** Public key of the payment destination */
202
+ payment_address: string;
203
+ }
204
+ /**
205
+ * Response containing estimated bundle fees.
206
+ */
207
+ export interface EstimateBundleFeeResponse {
208
+ /** Total bundle fee in lamports across all transactions */
209
+ fee_in_lamports: number;
210
+ /**
211
+ * Total bundle fee in the requested token (in decimals value of the token, e.g. 10^6 for USDC)
212
+ */
213
+ fee_in_token?: number;
134
214
  /** Public key of the signer used to estimate the fee */
135
215
  signer_pubkey: string;
136
216
  /** Public key of the payment destination */
@@ -226,8 +306,12 @@ export interface EnabledMethods {
226
306
  liveness: boolean;
227
307
  /** Whether the estimate_transaction_fee method is enabled */
228
308
  estimate_transaction_fee: boolean;
309
+ /** Whether the estimate_bundle_fee method is enabled (requires bundle.enabled = true) */
310
+ estimate_bundle_fee: boolean;
229
311
  /** Whether the get_supported_tokens method is enabled */
230
312
  get_supported_tokens: boolean;
313
+ /** Whether the get_payer_signer method is enabled */
314
+ get_payer_signer: boolean;
231
315
  /** Whether the sign_transaction method is enabled */
232
316
  sign_transaction: boolean;
233
317
  /** Whether the sign_and_send_transaction method is enabled */
@@ -238,6 +322,12 @@ export interface EnabledMethods {
238
322
  get_blockhash: boolean;
239
323
  /** Whether the get_config method is enabled */
240
324
  get_config: boolean;
325
+ /** Whether the get_version method is enabled */
326
+ get_version: boolean;
327
+ /** Whether the sign_and_send_bundle method is enabled (requires bundle.enabled = true) */
328
+ sign_and_send_bundle: boolean;
329
+ /** Whether the sign_bundle method is enabled (requires bundle.enabled = true) */
330
+ sign_bundle: boolean;
241
331
  }
242
332
  /**
243
333
  * Kora server configuration.
@@ -296,6 +386,12 @@ export interface SplTokenInstructionPolicy {
296
386
  allow_set_authority: boolean;
297
387
  /** Allow fee payer to mint SPL tokens */
298
388
  allow_mint_to: boolean;
389
+ /** Allow fee payer to initialize SPL token mints */
390
+ allow_initialize_mint: boolean;
391
+ /** Allow fee payer to initialize SPL token accounts */
392
+ allow_initialize_account: boolean;
393
+ /** Allow fee payer to initialize SPL multisig accounts */
394
+ allow_initialize_multisig: boolean;
299
395
  /** Allow fee payer to freeze SPL token accounts */
300
396
  allow_freeze_account: boolean;
301
397
  /** Allow fee payer to thaw SPL token accounts */
@@ -319,6 +415,12 @@ export interface Token2022InstructionPolicy {
319
415
  allow_set_authority: boolean;
320
416
  /** Allow fee payer to mint Token2022 tokens */
321
417
  allow_mint_to: boolean;
418
+ /** Allow fee payer to initialize Token2022 mints */
419
+ allow_initialize_mint: boolean;
420
+ /** Allow fee payer to initialize Token2022 accounts */
421
+ allow_initialize_account: boolean;
422
+ /** Allow fee payer to initialize Token2022 multisig accounts */
423
+ allow_initialize_multisig: boolean;
322
424
  /** Allow fee payer to freeze Token2022 accounts */
323
425
  allow_freeze_account: boolean;
324
426
  /** Allow fee payer to thaw Token2022 accounts */
@@ -1,32 +1,34 @@
1
1
  import setupTestSuite from './setup.js';
2
2
  import { runAuthenticationTests } from './auth-setup.js';
3
- import { generateKeyPairSigner, getBase64EncodedWireTransaction, getBase64Encoder, getTransactionDecoder, signTransaction, } from '@solana/kit';
4
- import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
3
+ import { getBase64Decoder, getBase64Encoder, getTransactionDecoder, getTransactionEncoder, partiallySignTransaction, } from '@solana/kit';
4
+ import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
5
5
  function transactionFromBase64(base64) {
6
6
  const encoder = getBase64Encoder();
7
7
  const decoder = getTransactionDecoder();
8
8
  const messageBytes = encoder.encode(base64);
9
9
  return decoder.decode(messageBytes);
10
10
  }
11
+ function transactionToBase64(transaction) {
12
+ const txEncoder = getTransactionEncoder();
13
+ const txBytes = txEncoder.encode(transaction);
14
+ const base64Decoder = getBase64Decoder();
15
+ return base64Decoder.decode(txBytes);
16
+ }
11
17
  const AUTH_ENABLED = process.env.ENABLE_AUTH === 'true';
12
18
  const KORA_SIGNER_TYPE = process.env.KORA_SIGNER_TYPE || 'memory';
13
19
  describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without auth'} | signer type: ${KORA_SIGNER_TYPE})`, () => {
14
20
  let client;
15
21
  let testWallet;
16
22
  let testWalletAddress;
17
- let destinationAddress;
18
23
  let usdcMint;
19
24
  let koraAddress;
20
- let koraRpcUrl;
21
25
  beforeAll(async () => {
22
26
  const testSuite = await setupTestSuite();
23
27
  client = testSuite.koraClient;
24
28
  testWallet = testSuite.testWallet;
25
29
  testWalletAddress = testWallet.address;
26
- destinationAddress = testSuite.destinationAddress;
27
30
  usdcMint = testSuite.usdcMint;
28
31
  koraAddress = testSuite.koraAddress;
29
- koraRpcUrl = testSuite.koraRpcUrl;
30
32
  }, 90000); // allow adequate time for airdrops and token initialization
31
33
  // Run authentication tests only when auth is enabled
32
34
  if (AUTH_ENABLED) {
@@ -91,6 +93,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
91
93
  expect(config.enabled_methods.transfer_transaction).toBeDefined();
92
94
  expect(config.enabled_methods.get_blockhash).toBeDefined();
93
95
  expect(config.enabled_methods.get_config).toBeDefined();
96
+ expect(config.enabled_methods.get_version).toBeDefined();
94
97
  });
95
98
  it('should get payer signer', async () => {
96
99
  const { signer_address, payment_address } = await client.getPayerSigner();
@@ -110,43 +113,29 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
110
113
  expect(blockhash.length).toBeGreaterThanOrEqual(43);
111
114
  expect(blockhash.length).toBeLessThanOrEqual(44); // Base58 encoded hash length
112
115
  });
116
+ it('should get version', async () => {
117
+ const { version } = await client.getVersion();
118
+ expect(version).toBeDefined();
119
+ expect(typeof version).toBe('string');
120
+ expect(version.length).toBeGreaterThan(0);
121
+ // Version should follow semver format (e.g., "2.1.0" or "2.1.0-beta.0")
122
+ expect(version).toMatch(/^\d+\.\d+\.\d+/);
123
+ });
113
124
  });
114
125
  describe('Transaction Operations', () => {
115
- it('should create transfer transaction', async () => {
116
- const request = {
117
- amount: 1000000, // 1 USDC
118
- token: usdcMint,
119
- source: testWalletAddress,
120
- destination: destinationAddress,
121
- };
122
- const response = await client.transferTransaction(request);
123
- expect(response).toBeDefined();
124
- expect(response.transaction).toBeDefined();
125
- expect(response.blockhash).toBeDefined();
126
- expect(response.message).toBeDefined();
127
- expect(response.instructions).toBeDefined();
128
- // since setup created ATA for destination, we should not expect ata instruction, only transfer instruction
129
- expect(response.instructions?.length).toBe(1);
130
- expect(response.instructions?.[0].programAddress).toBe(TOKEN_PROGRAM_ADDRESS);
131
- });
132
- it('should create transfer transaction to address with no ATA', async () => {
133
- const randomDestination = await generateKeyPairSigner();
126
+ it('should create transfer transaction (DEPRECATED endpoint)', async () => {
134
127
  const request = {
135
128
  amount: 1000000, // 1 USDC
136
129
  token: usdcMint,
137
130
  source: testWalletAddress,
138
- destination: randomDestination.address,
131
+ destination: koraAddress, // user specifies destination
139
132
  };
140
133
  const response = await client.transferTransaction(request);
141
134
  expect(response).toBeDefined();
142
135
  expect(response.transaction).toBeDefined();
143
136
  expect(response.blockhash).toBeDefined();
144
137
  expect(response.message).toBeDefined();
145
- expect(response.instructions).toBeDefined();
146
- // since setup created ATA for destination, we should not expect ata instruction, only transfer instruction
147
- expect(response.instructions?.length).toBe(2);
148
- expect(response.instructions?.[0].programAddress).toBe(ASSOCIATED_TOKEN_PROGRAM_ADDRESS);
149
- expect(response.instructions?.[1].programAddress).toBe(TOKEN_PROGRAM_ADDRESS);
138
+ expect(response.signer_pubkey).toBeDefined();
150
139
  });
151
140
  it('should estimate transaction fee', async () => {
152
141
  // First create a transaction
@@ -154,7 +143,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
154
143
  amount: 1000000,
155
144
  token: usdcMint,
156
145
  source: testWalletAddress,
157
- destination: testWalletAddress,
146
+ destination: koraAddress,
158
147
  };
159
148
  const { transaction } = await client.transferTransaction(transferRequest);
160
149
  const fee = await client.estimateTransactionFee({ transaction, fee_token: usdcMint });
@@ -165,13 +154,11 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
165
154
  expect(fee.fee_in_token).toBeGreaterThan(0);
166
155
  });
167
156
  it('should sign transaction', async () => {
168
- const config = await client.getConfig();
169
- const paymentAddress = config.fee_payers[0];
170
157
  const transferRequest = {
171
158
  amount: 1000000,
172
159
  token: usdcMint,
173
160
  source: testWalletAddress,
174
- destination: paymentAddress,
161
+ destination: koraAddress,
175
162
  };
176
163
  const { transaction } = await client.transferTransaction(transferRequest);
177
164
  const signResult = await client.signTransaction({
@@ -181,19 +168,18 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
181
168
  expect(signResult.signed_transaction).toBeDefined();
182
169
  });
183
170
  it('should sign and send transaction', async () => {
184
- const config = await client.getConfig();
185
- const paymentAddress = config.fee_payers[0];
186
171
  const transferRequest = {
187
172
  amount: 1000000,
188
173
  token: usdcMint,
189
174
  source: testWalletAddress,
190
- destination: paymentAddress,
175
+ destination: koraAddress,
191
176
  };
192
177
  const { transaction: transactionString } = await client.transferTransaction(transferRequest);
193
178
  const transaction = transactionFromBase64(transactionString);
194
- // Sign transaction with test wallet before sending
195
- const signedTransaction = await signTransaction([testWallet.keyPair], transaction);
196
- const base64SignedTransaction = getBase64EncodedWireTransaction(signedTransaction);
179
+ // Partially sign transaction with test wallet before sending
180
+ // Kora will add fee payer signature via signAndSendTransaction
181
+ const signedTransaction = await partiallySignTransaction([testWallet.keyPair], transaction);
182
+ const base64SignedTransaction = transactionToBase64(signedTransaction);
197
183
  const signResult = await client.signAndSendTransaction({
198
184
  transaction: base64SignedTransaction,
199
185
  });
@@ -205,7 +191,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
205
191
  amount: 1000000,
206
192
  token: usdcMint,
207
193
  source: testWalletAddress,
208
- destination: destinationAddress,
194
+ destination: koraAddress,
209
195
  };
210
196
  const [expectedSenderAta] = await findAssociatedTokenPda({
211
197
  owner: testWalletAddress,
@@ -236,13 +222,81 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
236
222
  expect(original_transaction).toBe(transaction);
237
223
  });
238
224
  });
225
+ describe('Bundle Operations', () => {
226
+ it('should sign bundle of transactions', async () => {
227
+ // Create two transfer transactions for the bundle
228
+ const transferRequest1 = {
229
+ amount: 1000000,
230
+ token: usdcMint,
231
+ source: testWalletAddress,
232
+ destination: koraAddress,
233
+ };
234
+ const transferRequest2 = {
235
+ amount: 500000,
236
+ token: usdcMint,
237
+ source: testWalletAddress,
238
+ destination: koraAddress,
239
+ };
240
+ const { transaction: tx1String } = await client.transferTransaction(transferRequest1);
241
+ const { transaction: tx2String } = await client.transferTransaction(transferRequest2);
242
+ // Partially sign both transactions with test wallet
243
+ const tx1 = transactionFromBase64(tx1String);
244
+ const tx2 = transactionFromBase64(tx2String);
245
+ const signedTx1 = await partiallySignTransaction([testWallet.keyPair], tx1);
246
+ const signedTx2 = await partiallySignTransaction([testWallet.keyPair], tx2);
247
+ const base64Tx1 = transactionToBase64(signedTx1);
248
+ const base64Tx2 = transactionToBase64(signedTx2);
249
+ const result = await client.signBundle({
250
+ transactions: [base64Tx1, base64Tx2],
251
+ });
252
+ expect(result).toBeDefined();
253
+ expect(result.signed_transactions).toBeDefined();
254
+ expect(Array.isArray(result.signed_transactions)).toBe(true);
255
+ expect(result.signed_transactions.length).toBe(2);
256
+ expect(result.signer_pubkey).toBeDefined();
257
+ });
258
+ it('should sign and send bundle of transactions', async () => {
259
+ // Create two transfer transactions for the bundle
260
+ const transferRequest1 = {
261
+ amount: 1000000,
262
+ token: usdcMint,
263
+ source: testWalletAddress,
264
+ destination: koraAddress,
265
+ };
266
+ const transferRequest2 = {
267
+ amount: 500000,
268
+ token: usdcMint,
269
+ source: testWalletAddress,
270
+ destination: koraAddress,
271
+ };
272
+ const { transaction: tx1String } = await client.transferTransaction(transferRequest1);
273
+ const { transaction: tx2String } = await client.transferTransaction(transferRequest2);
274
+ // Partially sign both transactions with test wallet
275
+ const tx1 = transactionFromBase64(tx1String);
276
+ const tx2 = transactionFromBase64(tx2String);
277
+ const signedTx1 = await partiallySignTransaction([testWallet.keyPair], tx1);
278
+ const signedTx2 = await partiallySignTransaction([testWallet.keyPair], tx2);
279
+ const base64Tx1 = transactionToBase64(signedTx1);
280
+ const base64Tx2 = transactionToBase64(signedTx2);
281
+ const result = await client.signAndSendBundle({
282
+ transactions: [base64Tx1, base64Tx2],
283
+ });
284
+ expect(result).toBeDefined();
285
+ expect(result.signed_transactions).toBeDefined();
286
+ expect(Array.isArray(result.signed_transactions)).toBe(true);
287
+ expect(result.signed_transactions.length).toBe(2);
288
+ expect(result.signer_pubkey).toBeDefined();
289
+ expect(result.bundle_uuid).toBeDefined();
290
+ expect(typeof result.bundle_uuid).toBe('string');
291
+ });
292
+ });
239
293
  describe('Error Handling', () => {
240
294
  it('should handle invalid token address', async () => {
241
295
  const request = {
242
296
  amount: 1000000,
243
297
  token: 'InvalidTokenAddress',
244
298
  source: testWalletAddress,
245
- destination: destinationAddress,
299
+ destination: koraAddress,
246
300
  };
247
301
  await expect(client.transferTransaction(request)).rejects.toThrow();
248
302
  });
@@ -251,7 +305,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
251
305
  amount: -1, // Invalid amount
252
306
  token: usdcMint,
253
307
  source: testWalletAddress,
254
- destination: destinationAddress,
308
+ destination: koraAddress,
255
309
  };
256
310
  await expect(client.transferTransaction(request)).rejects.toThrow();
257
311
  });
@@ -268,7 +322,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
268
322
  amount: 1000000,
269
323
  token: usdcMint,
270
324
  source: testWalletAddress,
271
- destination: destinationAddress,
325
+ destination: koraAddress,
272
326
  };
273
327
  // TODO: API has an error. this endpoint should verify the provided fee token is supported
274
328
  const { transaction } = await client.transferTransaction(transferRequest);
@@ -280,13 +334,11 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
280
334
  });
281
335
  describe('End-to-End Flows', () => {
282
336
  it('should handle transfer and sign flow', async () => {
283
- const config = await client.getConfig();
284
- const paymentAddress = config.fee_payers[0];
285
337
  const request = {
286
338
  amount: 1000000,
287
339
  token: usdcMint,
288
340
  source: testWalletAddress,
289
- destination: paymentAddress,
341
+ destination: koraAddress,
290
342
  };
291
343
  // Create and sign the transaction
292
344
  const { transaction } = await client.transferTransaction(request);
@@ -299,7 +351,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
299
351
  amount: 1000000,
300
352
  token: invalidTokenMint,
301
353
  source: testWalletAddress,
302
- destination: destinationAddress,
354
+ destination: koraAddress,
303
355
  };
304
356
  await expect(client.transferTransaction(request)).rejects.toThrow();
305
357
  });
@@ -108,6 +108,9 @@ describe('KoraClient Unit Tests', () => {
108
108
  allow_revoke: true,
109
109
  allow_set_authority: true,
110
110
  allow_mint_to: true,
111
+ allow_initialize_mint: true,
112
+ allow_initialize_account: true,
113
+ allow_initialize_multisig: true,
111
114
  allow_freeze_account: true,
112
115
  allow_thaw_account: true,
113
116
  },
@@ -119,6 +122,9 @@ describe('KoraClient Unit Tests', () => {
119
122
  allow_revoke: true,
120
123
  allow_set_authority: true,
121
124
  allow_mint_to: true,
125
+ allow_initialize_mint: true,
126
+ allow_initialize_account: true,
127
+ allow_initialize_multisig: true,
122
128
  allow_freeze_account: true,
123
129
  allow_thaw_account: true,
124
130
  },
@@ -135,12 +141,17 @@ describe('KoraClient Unit Tests', () => {
135
141
  enabled_methods: {
136
142
  liveness: true,
137
143
  estimate_transaction_fee: true,
144
+ estimate_bundle_fee: true,
138
145
  get_supported_tokens: true,
146
+ get_payer_signer: true,
139
147
  sign_transaction: true,
140
148
  sign_and_send_transaction: true,
141
149
  transfer_transaction: true,
142
150
  get_blockhash: true,
143
151
  get_config: true,
152
+ get_version: true,
153
+ sign_and_send_bundle: true,
154
+ sign_bundle: true,
144
155
  },
145
156
  };
146
157
  await testSuccessfulRpcMethod('getConfig', () => client.getConfig(), mockConfig);
@@ -154,6 +165,14 @@ describe('KoraClient Unit Tests', () => {
154
165
  await testSuccessfulRpcMethod('getBlockhash', () => client.getBlockhash(), mockResponse);
155
166
  });
156
167
  });
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
+ });
157
176
  describe('getSupportedTokens', () => {
158
177
  it('should return supported tokens list', async () => {
159
178
  const mockResponse = {
@@ -219,48 +238,49 @@ describe('KoraClient Unit Tests', () => {
219
238
  await testSuccessfulRpcMethod('signAndSendTransaction', () => client.signAndSendTransaction(request), mockResponse, request);
220
239
  });
221
240
  });
222
- describe('transferTransaction', () => {
223
- it('should create transfer transaction', async () => {
241
+ describe('signBundle', () => {
242
+ it('should sign bundle of transactions', async () => {
224
243
  const request = {
225
- amount: 1000000,
226
- token: 'SOL',
227
- source: 'source_address',
228
- destination: 'destination_address',
244
+ transactions: ['base64_tx_1', 'base64_tx_2'],
229
245
  };
230
246
  const mockResponse = {
231
- transaction: 'base64_encoded_transaction',
232
- message: 'Transfer transaction created',
233
- blockhash: 'test_blockhash',
247
+ signed_transactions: ['base64_signed_tx_1', 'base64_signed_tx_2'],
234
248
  signer_pubkey: 'test_signer_pubkey',
235
- instructions: [],
236
249
  };
237
- await testSuccessfulRpcMethod('transferTransaction', () => client.transferTransaction(request), mockResponse, request);
250
+ await testSuccessfulRpcMethod('signBundle', () => client.signBundle(request), mockResponse, request);
238
251
  });
239
- it('should parse instructions from transfer transaction message', async () => {
252
+ it('should handle RPC error', async () => {
240
253
  const request = {
241
- amount: 1000000,
242
- token: 'SOL',
243
- source: 'source_address',
244
- destination: 'destination_address',
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');
259
+ });
260
+ });
261
+ describe('signAndSendBundle', () => {
262
+ it('should sign and send bundle of transactions', async () => {
263
+ const request = {
264
+ transactions: ['base64_tx_1', 'base64_tx_2'],
245
265
  };
246
- // This is a real base64 encoded message for testing
247
- // In production, this would come from the RPC response
248
- const mockMessage = 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDAAEMAgAAAAEAAAAAAAAA';
249
266
  const mockResponse = {
250
- transaction: 'base64_encoded_transaction',
251
- message: mockMessage,
252
- blockhash: 'test_blockhash',
267
+ signed_transactions: ['base64_signed_tx_1', 'base64_signed_tx_2'],
253
268
  signer_pubkey: 'test_signer_pubkey',
254
- instructions: [],
269
+ bundle_uuid: 'test-bundle-uuid-123',
255
270
  };
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 () => {
271
+ await testSuccessfulRpcMethod('signAndSendBundle', () => client.signAndSendBundle(request), mockResponse, request);
272
+ });
273
+ it('should handle RPC error', async () => {
274
+ const request = {
275
+ transactions: ['base64_tx_1'],
276
+ };
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');
280
+ });
281
+ });
282
+ describe('transferTransaction (DEPRECATED)', () => {
283
+ it('should create transfer transaction', async () => {
264
284
  const request = {
265
285
  amount: 1000000,
266
286
  token: 'SOL',
@@ -269,15 +289,12 @@ describe('KoraClient Unit Tests', () => {
269
289
  };
270
290
  const mockResponse = {
271
291
  transaction: 'base64_encoded_transaction',
272
- message: '',
292
+ message: 'Transfer transaction created',
273
293
  blockhash: 'test_blockhash',
274
294
  signer_pubkey: 'test_signer_pubkey',
275
295
  instructions: [],
276
296
  };
277
- mockSuccessfulResponse(mockResponse);
278
- const result = await client.transferTransaction(request);
279
- // Should handle empty message gracefully
280
- expect(result.instructions).toEqual([]);
297
+ await testSuccessfulRpcMethod('transferTransaction', () => client.transferTransaction(request), mockResponse, request);
281
298
  });
282
299
  });
283
300
  describe('getPaymentInstruction', () => {
@@ -312,6 +329,9 @@ describe('KoraClient Unit Tests', () => {
312
329
  allow_revoke: true,
313
330
  allow_set_authority: true,
314
331
  allow_mint_to: true,
332
+ allow_initialize_mint: true,
333
+ allow_initialize_account: true,
334
+ allow_initialize_multisig: true,
315
335
  allow_freeze_account: true,
316
336
  allow_thaw_account: true,
317
337
  },
@@ -323,6 +343,9 @@ describe('KoraClient Unit Tests', () => {
323
343
  allow_revoke: true,
324
344
  allow_set_authority: true,
325
345
  allow_mint_to: true,
346
+ allow_initialize_mint: true,
347
+ allow_initialize_account: true,
348
+ allow_initialize_multisig: true,
326
349
  allow_freeze_account: true,
327
350
  allow_thaw_account: true,
328
351
  },
@@ -339,12 +362,17 @@ describe('KoraClient Unit Tests', () => {
339
362
  enabled_methods: {
340
363
  liveness: true,
341
364
  estimate_transaction_fee: true,
365
+ estimate_bundle_fee: true,
342
366
  get_supported_tokens: true,
367
+ get_payer_signer: true,
343
368
  sign_transaction: true,
344
369
  sign_and_send_transaction: true,
345
370
  transfer_transaction: true,
346
371
  get_blockhash: true,
347
372
  get_config: true,
373
+ get_version: true,
374
+ sign_and_send_bundle: true,
375
+ sign_bundle: true,
348
376
  },
349
377
  };
350
378
  const mockFeeEstimate = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana/kora",
3
- "version": "0.2.0-beta.0",
3
+ "version": "0.2.0-beta.2",
4
4
  "description": "TypeScript SDK for Kora RPC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",