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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,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, 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
@@ -133,13 +145,55 @@ export declare class KoraClient {
133
145
  */
134
146
  signAndSendTransaction(request: SignAndSendTransactionRequest): Promise<SignAndSendTransactionResponse>;
135
147
  /**
136
- * Creates a token transfer transaction with Kora as the fee payer.
148
+ * Signs a bundle of transactions with the Kora fee payer without broadcasting.
149
+ * @param request - Sign bundle request parameters
150
+ * @param request.transactions - Array of base64-encoded transactions to sign
151
+ * @param request.signer_key - Optional signer address for the transactions
152
+ * @param request.sig_verify - Optional signature verification (defaults to false)
153
+ * @returns Array of signed transactions and signer public key
154
+ * @throws {Error} When the RPC call fails or validation fails
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * const result = await client.signBundle({
159
+ * transactions: ['base64Tx1', 'base64Tx2']
160
+ * });
161
+ * console.log('Signed transactions:', result.signed_transactions);
162
+ * console.log('Signer:', result.signer_pubkey);
163
+ * ```
164
+ */
165
+ signBundle(request: SignBundleRequest): Promise<SignBundleResponse>;
166
+ /**
167
+ * Signs a bundle of transactions and sends them to Jito block engine.
168
+ * @param request - Sign and send bundle request parameters
169
+ * @param request.transactions - Array of base64-encoded transactions to sign and send
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, signer public key, and Jito bundle UUID
173
+ * @throws {Error} When the RPC call fails, validation fails, or Jito submission fails
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const result = await client.signAndSendBundle({
178
+ * transactions: ['base64Tx1', 'base64Tx2']
179
+ * });
180
+ * console.log('Bundle UUID:', result.bundle_uuid);
181
+ * console.log('Signed transactions:', result.signed_transactions);
182
+ * ```
183
+ */
184
+ signAndSendBundle(request: SignAndSendBundleRequest): Promise<SignAndSendBundleResponse>;
185
+ /**
186
+ * Creates an unsigned transfer transaction.
187
+ *
188
+ * @deprecated Use `getPaymentInstruction` instead for fee payment flows.
189
+ *
137
190
  * @param request - Transfer request parameters
138
191
  * @param request.amount - Amount to transfer (in token's smallest unit)
139
192
  * @param request.token - Mint address of the token to transfer
140
193
  * @param request.source - Source wallet public key
141
194
  * @param request.destination - Destination wallet public key
142
- * @returns Base64-encoded signed transaction, base64-encoded message, blockhash, and parsed instructions
195
+ * @param request.signer_key - Optional signer key to select specific Kora signer
196
+ * @returns Unsigned transaction, message, blockhash, and signer info
143
197
  * @throws {Error} When the RPC call fails or token is not supported
144
198
  *
145
199
  * @example
@@ -148,11 +202,9 @@ export declare class KoraClient {
148
202
  * amount: 1000000, // 1 USDC (6 decimals)
149
203
  * token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
150
204
  * source: 'sourceWalletPublicKey',
151
- * destination: 'destinationWalletPublicKey'
205
+ * destination: 'destinationWalletPublicKey',
152
206
  * });
153
- * console.log('Transaction:', transfer.transaction);
154
- * console.log('Message:', transfer.message);
155
- * console.log('Instructions:', transfer.instructions);
207
+ * console.log('Signer:', transfer.signer_pubkey);
156
208
  * ```
157
209
  */
158
210
  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
@@ -191,13 +205,59 @@ export class KoraClient {
191
205
  return this.rpcRequest('signAndSendTransaction', request);
192
206
  }
193
207
  /**
194
- * Creates a token transfer transaction with Kora as the fee payer.
208
+ * Signs a bundle of transactions with the Kora fee payer without broadcasting.
209
+ * @param request - Sign bundle request parameters
210
+ * @param request.transactions - Array of base64-encoded transactions to sign
211
+ * @param request.signer_key - Optional signer address for the transactions
212
+ * @param request.sig_verify - Optional signature verification (defaults to false)
213
+ * @returns Array of signed transactions and signer public key
214
+ * @throws {Error} When the RPC call fails or validation fails
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const result = await client.signBundle({
219
+ * transactions: ['base64Tx1', 'base64Tx2']
220
+ * });
221
+ * console.log('Signed transactions:', result.signed_transactions);
222
+ * console.log('Signer:', result.signer_pubkey);
223
+ * ```
224
+ */
225
+ async signBundle(request) {
226
+ return this.rpcRequest('signBundle', request);
227
+ }
228
+ /**
229
+ * Signs a bundle of transactions and sends them to Jito block engine.
230
+ * @param request - Sign and send bundle request parameters
231
+ * @param request.transactions - Array of base64-encoded transactions to sign and send
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, signer public key, and Jito bundle UUID
235
+ * @throws {Error} When the RPC call fails, validation fails, or Jito submission fails
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * const result = await client.signAndSendBundle({
240
+ * transactions: ['base64Tx1', 'base64Tx2']
241
+ * });
242
+ * console.log('Bundle UUID:', result.bundle_uuid);
243
+ * console.log('Signed transactions:', result.signed_transactions);
244
+ * ```
245
+ */
246
+ async signAndSendBundle(request) {
247
+ return this.rpcRequest('signAndSendBundle', request);
248
+ }
249
+ /**
250
+ * Creates an unsigned transfer transaction.
251
+ *
252
+ * @deprecated Use `getPaymentInstruction` instead for fee payment flows.
253
+ *
195
254
  * @param request - Transfer request parameters
196
255
  * @param request.amount - Amount to transfer (in token's smallest unit)
197
256
  * @param request.token - Mint address of the token to transfer
198
257
  * @param request.source - Source wallet public key
199
258
  * @param request.destination - Destination wallet public key
200
- * @returns Base64-encoded signed transaction, base64-encoded message, blockhash, and parsed instructions
259
+ * @param request.signer_key - Optional signer key to select specific Kora signer
260
+ * @returns Unsigned transaction, message, blockhash, and signer info
201
261
  * @throws {Error} When the RPC call fails or token is not supported
202
262
  *
203
263
  * @example
@@ -206,11 +266,9 @@ export class KoraClient {
206
266
  * amount: 1000000, // 1 USDC (6 decimals)
207
267
  * token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
208
268
  * source: 'sourceWalletPublicKey',
209
- * destination: 'destinationWalletPublicKey'
269
+ * destination: 'destinationWalletPublicKey',
210
270
  * });
211
- * console.log('Transaction:', transfer.transaction);
212
- * console.log('Message:', transfer.message);
213
- * console.log('Instructions:', transfer.instructions);
271
+ * console.log('Signer:', transfer.signer_pubkey);
214
272
  * ```
215
273
  */
216
274
  async transferTransaction(request) {
@@ -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
  */
@@ -74,15 +97,17 @@ export interface GetPaymentInstructionRequest {
74
97
  */
75
98
  /**
76
99
  * Response from creating a transfer transaction.
100
+ * The transaction is unsigned.
101
+ * @deprecated Use `getPaymentInstruction` instead for fee payment flows.
77
102
  */
78
103
  export interface TransferTransactionResponse {
79
- /** Base64-encoded signed transaction */
104
+ /** Base64-encoded unsigned transaction */
80
105
  transaction: string;
81
- /** Base64-encoded message */
106
+ /** Base64-encoded unsigned message */
82
107
  message: string;
83
108
  /** Recent blockhash used in the transaction */
84
109
  blockhash: string;
85
- /** Public key of the signer used to send the transaction */
110
+ /** Public key of the Kora signer (fee payer) */
86
111
  signer_pubkey: string;
87
112
  /** Parsed instructions from the transaction message */
88
113
  instructions: Instruction[];
@@ -107,6 +132,26 @@ export interface SignAndSendTransactionResponse {
107
132
  /** Public key of the signer used to send the transaction */
108
133
  signer_pubkey: string;
109
134
  }
135
+ /**
136
+ * Response from signing a bundle of transactions.
137
+ */
138
+ export interface SignBundleResponse {
139
+ /** Array of base64-encoded signed transactions */
140
+ signed_transactions: string[];
141
+ /** Public key of the signer used to sign the transactions */
142
+ signer_pubkey: string;
143
+ }
144
+ /**
145
+ * Response from signing and sending a bundle of transactions via Jito.
146
+ */
147
+ export interface SignAndSendBundleResponse {
148
+ /** Array of base64-encoded signed transactions */
149
+ signed_transactions: string[];
150
+ /** Public key of the signer used to sign the transactions */
151
+ signer_pubkey: string;
152
+ /** UUID of the submitted Jito bundle */
153
+ bundle_uuid: string;
154
+ }
110
155
  /**
111
156
  * Response containing the latest blockhash.
112
157
  */
@@ -114,6 +159,13 @@ export interface GetBlockhashResponse {
114
159
  /** Base58-encoded blockhash */
115
160
  blockhash: string;
116
161
  }
162
+ /**
163
+ * Response containing the server version.
164
+ */
165
+ export interface GetVersionResponse {
166
+ /** Server version string */
167
+ version: string;
168
+ }
117
169
  /**
118
170
  * Response containing supported token mint addresses.
119
171
  */
@@ -228,6 +280,8 @@ export interface EnabledMethods {
228
280
  estimate_transaction_fee: boolean;
229
281
  /** Whether the get_supported_tokens method is enabled */
230
282
  get_supported_tokens: boolean;
283
+ /** Whether the get_payer_signer method is enabled */
284
+ get_payer_signer: boolean;
231
285
  /** Whether the sign_transaction method is enabled */
232
286
  sign_transaction: boolean;
233
287
  /** Whether the sign_and_send_transaction method is enabled */
@@ -238,6 +292,12 @@ export interface EnabledMethods {
238
292
  get_blockhash: boolean;
239
293
  /** Whether the get_config method is enabled */
240
294
  get_config: boolean;
295
+ /** Whether the get_version method is enabled */
296
+ get_version: boolean;
297
+ /** Whether the sign_and_send_bundle method is enabled (requires bundle.enabled = true) */
298
+ sign_and_send_bundle: boolean;
299
+ /** Whether the sign_bundle method is enabled (requires bundle.enabled = true) */
300
+ sign_bundle: boolean;
241
301
  }
242
302
  /**
243
303
  * Kora server configuration.
@@ -296,6 +356,12 @@ export interface SplTokenInstructionPolicy {
296
356
  allow_set_authority: boolean;
297
357
  /** Allow fee payer to mint SPL tokens */
298
358
  allow_mint_to: boolean;
359
+ /** Allow fee payer to initialize SPL token mints */
360
+ allow_initialize_mint: boolean;
361
+ /** Allow fee payer to initialize SPL token accounts */
362
+ allow_initialize_account: boolean;
363
+ /** Allow fee payer to initialize SPL multisig accounts */
364
+ allow_initialize_multisig: boolean;
299
365
  /** Allow fee payer to freeze SPL token accounts */
300
366
  allow_freeze_account: boolean;
301
367
  /** Allow fee payer to thaw SPL token accounts */
@@ -319,6 +385,12 @@ export interface Token2022InstructionPolicy {
319
385
  allow_set_authority: boolean;
320
386
  /** Allow fee payer to mint Token2022 tokens */
321
387
  allow_mint_to: boolean;
388
+ /** Allow fee payer to initialize Token2022 mints */
389
+ allow_initialize_mint: boolean;
390
+ /** Allow fee payer to initialize Token2022 accounts */
391
+ allow_initialize_account: boolean;
392
+ /** Allow fee payer to initialize Token2022 multisig accounts */
393
+ allow_initialize_multisig: boolean;
322
394
  /** Allow fee payer to freeze Token2022 accounts */
323
395
  allow_freeze_account: boolean;
324
396
  /** 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
  },
@@ -136,11 +142,15 @@ describe('KoraClient Unit Tests', () => {
136
142
  liveness: true,
137
143
  estimate_transaction_fee: true,
138
144
  get_supported_tokens: true,
145
+ get_payer_signer: true,
139
146
  sign_transaction: true,
140
147
  sign_and_send_transaction: true,
141
148
  transfer_transaction: true,
142
149
  get_blockhash: true,
143
150
  get_config: true,
151
+ get_version: true,
152
+ sign_and_send_bundle: true,
153
+ sign_bundle: true,
144
154
  },
145
155
  };
146
156
  await testSuccessfulRpcMethod('getConfig', () => client.getConfig(), mockConfig);
@@ -154,6 +164,14 @@ describe('KoraClient Unit Tests', () => {
154
164
  await testSuccessfulRpcMethod('getBlockhash', () => client.getBlockhash(), mockResponse);
155
165
  });
156
166
  });
167
+ describe('getVersion', () => {
168
+ it('should return server version', async () => {
169
+ const mockResponse = {
170
+ version: '2.1.0-beta.0',
171
+ };
172
+ await testSuccessfulRpcMethod('getVersion', () => client.getVersion(), mockResponse);
173
+ });
174
+ });
157
175
  describe('getSupportedTokens', () => {
158
176
  it('should return supported tokens list', async () => {
159
177
  const mockResponse = {
@@ -219,48 +237,49 @@ describe('KoraClient Unit Tests', () => {
219
237
  await testSuccessfulRpcMethod('signAndSendTransaction', () => client.signAndSendTransaction(request), mockResponse, request);
220
238
  });
221
239
  });
222
- describe('transferTransaction', () => {
223
- it('should create transfer transaction', async () => {
240
+ describe('signBundle', () => {
241
+ it('should sign bundle of transactions', async () => {
224
242
  const request = {
225
- amount: 1000000,
226
- token: 'SOL',
227
- source: 'source_address',
228
- destination: 'destination_address',
243
+ transactions: ['base64_tx_1', 'base64_tx_2'],
229
244
  };
230
245
  const mockResponse = {
231
- transaction: 'base64_encoded_transaction',
232
- message: 'Transfer transaction created',
233
- blockhash: 'test_blockhash',
246
+ signed_transactions: ['base64_signed_tx_1', 'base64_signed_tx_2'],
234
247
  signer_pubkey: 'test_signer_pubkey',
235
- instructions: [],
236
248
  };
237
- await testSuccessfulRpcMethod('transferTransaction', () => client.transferTransaction(request), mockResponse, request);
249
+ await testSuccessfulRpcMethod('signBundle', () => client.signBundle(request), mockResponse, request);
238
250
  });
239
- it('should parse instructions from transfer transaction message', async () => {
251
+ it('should handle RPC error', async () => {
240
252
  const request = {
241
- amount: 1000000,
242
- token: 'SOL',
243
- source: 'source_address',
244
- destination: 'destination_address',
253
+ transactions: ['base64_tx_1'],
254
+ };
255
+ const mockError = { code: -32000, message: 'Bundle validation failed' };
256
+ mockErrorResponse(mockError);
257
+ await expect(client.signBundle(request)).rejects.toThrow('RPC Error -32000: Bundle validation failed');
258
+ });
259
+ });
260
+ describe('signAndSendBundle', () => {
261
+ it('should sign and send bundle of transactions', async () => {
262
+ const request = {
263
+ transactions: ['base64_tx_1', 'base64_tx_2'],
245
264
  };
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
265
  const mockResponse = {
250
- transaction: 'base64_encoded_transaction',
251
- message: mockMessage,
252
- blockhash: 'test_blockhash',
266
+ signed_transactions: ['base64_signed_tx_1', 'base64_signed_tx_2'],
253
267
  signer_pubkey: 'test_signer_pubkey',
254
- instructions: [],
268
+ bundle_uuid: 'test-bundle-uuid-123',
255
269
  };
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 () => {
270
+ await testSuccessfulRpcMethod('signAndSendBundle', () => client.signAndSendBundle(request), mockResponse, request);
271
+ });
272
+ it('should handle RPC error', async () => {
273
+ const request = {
274
+ transactions: ['base64_tx_1'],
275
+ };
276
+ const mockError = { code: -32000, message: 'Jito submission failed' };
277
+ mockErrorResponse(mockError);
278
+ await expect(client.signAndSendBundle(request)).rejects.toThrow('RPC Error -32000: Jito submission failed');
279
+ });
280
+ });
281
+ describe('transferTransaction (DEPRECATED)', () => {
282
+ it('should create transfer transaction', async () => {
264
283
  const request = {
265
284
  amount: 1000000,
266
285
  token: 'SOL',
@@ -269,15 +288,12 @@ describe('KoraClient Unit Tests', () => {
269
288
  };
270
289
  const mockResponse = {
271
290
  transaction: 'base64_encoded_transaction',
272
- message: '',
291
+ message: 'Transfer transaction created',
273
292
  blockhash: 'test_blockhash',
274
293
  signer_pubkey: 'test_signer_pubkey',
275
294
  instructions: [],
276
295
  };
277
- mockSuccessfulResponse(mockResponse);
278
- const result = await client.transferTransaction(request);
279
- // Should handle empty message gracefully
280
- expect(result.instructions).toEqual([]);
296
+ await testSuccessfulRpcMethod('transferTransaction', () => client.transferTransaction(request), mockResponse, request);
281
297
  });
282
298
  });
283
299
  describe('getPaymentInstruction', () => {
@@ -312,6 +328,9 @@ describe('KoraClient Unit Tests', () => {
312
328
  allow_revoke: true,
313
329
  allow_set_authority: true,
314
330
  allow_mint_to: true,
331
+ allow_initialize_mint: true,
332
+ allow_initialize_account: true,
333
+ allow_initialize_multisig: true,
315
334
  allow_freeze_account: true,
316
335
  allow_thaw_account: true,
317
336
  },
@@ -323,6 +342,9 @@ describe('KoraClient Unit Tests', () => {
323
342
  allow_revoke: true,
324
343
  allow_set_authority: true,
325
344
  allow_mint_to: true,
345
+ allow_initialize_mint: true,
346
+ allow_initialize_account: true,
347
+ allow_initialize_multisig: true,
326
348
  allow_freeze_account: true,
327
349
  allow_thaw_account: true,
328
350
  },
@@ -340,11 +362,15 @@ describe('KoraClient Unit Tests', () => {
340
362
  liveness: true,
341
363
  estimate_transaction_fee: true,
342
364
  get_supported_tokens: true,
365
+ get_payer_signer: true,
343
366
  sign_transaction: true,
344
367
  sign_and_send_transaction: true,
345
368
  transfer_transaction: true,
346
369
  get_blockhash: true,
347
370
  get_config: true,
371
+ get_version: true,
372
+ sign_and_send_bundle: true,
373
+ sign_bundle: true,
348
374
  },
349
375
  };
350
376
  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.1",
4
4
  "description": "TypeScript SDK for Kora RPC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",