@solana/kora 0.3.0-beta.0 → 0.3.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.
@@ -153,6 +153,11 @@ export declare class KoraClient {
153
153
  * Signs a transaction and immediately broadcasts it to the Solana network.
154
154
  * @param request - Sign and send request parameters
155
155
  * @param request.transaction - Base64-encoded transaction to sign and send
156
+ * @param request.respond_after - The lifecycle milestone to wait for before
157
+ * responding (defaults to `'confirmed'`): `'confirmed'` waits for on-chain confirmation,
158
+ * `'sent'` returns once the RPC node accepts the transaction, `'signed'` returns as soon
159
+ * as signing completes and broadcasts in the background (broadcast failures are logged
160
+ * server-side; rebroadcast the returned signed transaction if it never lands)
156
161
  * @returns Signature and the signed transaction
157
162
  * @throws {Error} When the RPC call fails, validation fails, or broadcast fails
158
163
  *
@@ -162,6 +167,12 @@ export declare class KoraClient {
162
167
  * transaction: 'base64EncodedTransaction'
163
168
  * });
164
169
  * console.log('Transaction signature:', result.signature);
170
+ *
171
+ * // Lowest latency: return as soon as signing completes
172
+ * const fast = await client.signAndSendTransaction({
173
+ * transaction: 'base64EncodedTransaction',
174
+ * respond_after: 'signed'
175
+ * });
165
176
  * ```
166
177
  */
167
178
  signAndSendTransaction(request: SignAndSendTransactionRequest): Promise<SignAndSendTransactionResponse>;
@@ -1,6 +1,7 @@
1
1
  import { assertIsAddress, isTransactionSigner } from '@solana/kit';
2
2
  import { findAssociatedTokenPda, getTransferInstruction, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
3
3
  import crypto from 'crypto';
4
+ import { KoraError } from './error.js';
4
5
  /**
5
6
  * Kora RPC client for interacting with the Kora paymaster service.
6
7
  *
@@ -79,8 +80,8 @@ export class KoraClient {
79
80
  });
80
81
  const json = (await response.json());
81
82
  if (json.error) {
82
- const error = json.error;
83
- throw new Error(`RPC Error ${error.code}: ${error.message}`);
83
+ const { code, message, data } = json.error;
84
+ throw new KoraError(code, message, data);
84
85
  }
85
86
  return json.result;
86
87
  }
@@ -217,6 +218,11 @@ export class KoraClient {
217
218
  * Signs a transaction and immediately broadcasts it to the Solana network.
218
219
  * @param request - Sign and send request parameters
219
220
  * @param request.transaction - Base64-encoded transaction to sign and send
221
+ * @param request.respond_after - The lifecycle milestone to wait for before
222
+ * responding (defaults to `'confirmed'`): `'confirmed'` waits for on-chain confirmation,
223
+ * `'sent'` returns once the RPC node accepts the transaction, `'signed'` returns as soon
224
+ * as signing completes and broadcasts in the background (broadcast failures are logged
225
+ * server-side; rebroadcast the returned signed transaction if it never lands)
220
226
  * @returns Signature and the signed transaction
221
227
  * @throws {Error} When the RPC call fails, validation fails, or broadcast fails
222
228
  *
@@ -226,6 +232,12 @@ export class KoraClient {
226
232
  * transaction: 'base64EncodedTransaction'
227
233
  * });
228
234
  * console.log('Transaction signature:', result.signature);
235
+ *
236
+ * // Lowest latency: return as soon as signing completes
237
+ * const fast = await client.signAndSendTransaction({
238
+ * transaction: 'base64EncodedTransaction',
239
+ * respond_after: 'signed'
240
+ * });
229
241
  * ```
230
242
  */
231
243
  async signAndSendTransaction(request) {
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Stable numeric error codes for RPC responses following JSON-RPC 2.0 spec (-32000 to -32099 for server errors).
3
+ */
4
+ export declare enum KoraErrorCode {
5
+ InvalidTransaction = -32000,
6
+ ValidationError = -32001,
7
+ UnsupportedFeeToken = -32002,
8
+ InsufficientFunds = -32003,
9
+ InvalidRequest = -32004,
10
+ FeeEstimationFailed = -32005,
11
+ TransactionExecutionFailed = -32006,
12
+ SigningError = -32020,
13
+ RateLimitExceeded = -32030,
14
+ UsageLimitExceeded = -32031,
15
+ Unauthorized = -32032,
16
+ SwapError = -32040,
17
+ TokenOperationError = -32041,
18
+ AccountNotFound = -32050,
19
+ JitoError = -32060,
20
+ RecaptchaError = -32061,
21
+ InternalServerError = -32090,
22
+ ConfigError = -32091,
23
+ SerializationError = -32092,
24
+ RpcError = -32093
25
+ }
26
+ /**
27
+ * Structured data returned in the JSON-RPC error object's `data` field.
28
+ */
29
+ export interface KoraErrorData {
30
+ error_type: string;
31
+ }
32
+ /**
33
+ * Represents a structured error returned by the Kora paymaster service.
34
+ */
35
+ export declare class KoraError extends Error {
36
+ /** The stable numeric error code */
37
+ readonly code: KoraErrorCode;
38
+ /** Optional structured data about the error */
39
+ readonly data?: KoraErrorData;
40
+ constructor(code?: number, message?: string, data?: KoraErrorData);
41
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Stable numeric error codes for RPC responses following JSON-RPC 2.0 spec (-32000 to -32099 for server errors).
3
+ */
4
+ export var KoraErrorCode;
5
+ (function (KoraErrorCode) {
6
+ // Validation errors (-32000 to -32019)
7
+ KoraErrorCode[KoraErrorCode["InvalidTransaction"] = -32000] = "InvalidTransaction";
8
+ KoraErrorCode[KoraErrorCode["ValidationError"] = -32001] = "ValidationError";
9
+ KoraErrorCode[KoraErrorCode["UnsupportedFeeToken"] = -32002] = "UnsupportedFeeToken";
10
+ KoraErrorCode[KoraErrorCode["InsufficientFunds"] = -32003] = "InsufficientFunds";
11
+ KoraErrorCode[KoraErrorCode["InvalidRequest"] = -32004] = "InvalidRequest";
12
+ KoraErrorCode[KoraErrorCode["FeeEstimationFailed"] = -32005] = "FeeEstimationFailed";
13
+ KoraErrorCode[KoraErrorCode["TransactionExecutionFailed"] = -32006] = "TransactionExecutionFailed";
14
+ // Signing errors (-32020 to -32029)
15
+ KoraErrorCode[KoraErrorCode["SigningError"] = -32020] = "SigningError";
16
+ // Auth / Rate limiting (-32030 to -32039)
17
+ KoraErrorCode[KoraErrorCode["RateLimitExceeded"] = -32030] = "RateLimitExceeded";
18
+ KoraErrorCode[KoraErrorCode["UsageLimitExceeded"] = -32031] = "UsageLimitExceeded";
19
+ KoraErrorCode[KoraErrorCode["Unauthorized"] = -32032] = "Unauthorized";
20
+ // Token / Swap (-32040 to -32049)
21
+ KoraErrorCode[KoraErrorCode["SwapError"] = -32040] = "SwapError";
22
+ KoraErrorCode[KoraErrorCode["TokenOperationError"] = -32041] = "TokenOperationError";
23
+ // Account errors (-32050 to -32059)
24
+ KoraErrorCode[KoraErrorCode["AccountNotFound"] = -32050] = "AccountNotFound";
25
+ // External services (-32060 to -32069)
26
+ KoraErrorCode[KoraErrorCode["JitoError"] = -32060] = "JitoError";
27
+ KoraErrorCode[KoraErrorCode["RecaptchaError"] = -32061] = "RecaptchaError";
28
+ // Internal errors (-32090 to -32099)
29
+ KoraErrorCode[KoraErrorCode["InternalServerError"] = -32090] = "InternalServerError";
30
+ KoraErrorCode[KoraErrorCode["ConfigError"] = -32091] = "ConfigError";
31
+ KoraErrorCode[KoraErrorCode["SerializationError"] = -32092] = "SerializationError";
32
+ KoraErrorCode[KoraErrorCode["RpcError"] = -32093] = "RpcError";
33
+ })(KoraErrorCode || (KoraErrorCode = {}));
34
+ /**
35
+ * Represents a structured error returned by the Kora paymaster service.
36
+ */
37
+ export class KoraError extends Error {
38
+ /** The stable numeric error code */
39
+ code;
40
+ /** Optional structured data about the error */
41
+ data;
42
+ constructor(code, message, data) {
43
+ const finalCode = code ?? -32603;
44
+ const finalMessage = message ?? 'Unknown error';
45
+ super(`Kora Error ${finalCode}: ${finalMessage}`);
46
+ this.name = 'KoraError';
47
+ this.code = finalCode;
48
+ this.data = data;
49
+ // Ensure proper stack trace in environments that support Error.captureStackTrace
50
+ if (Error.captureStackTrace) {
51
+ Error.captureStackTrace(this, KoraError);
52
+ }
53
+ }
54
+ }
@@ -1,4 +1,4 @@
1
+ export * from './error.js';
1
2
  export * from './types/index.js';
2
3
  export { KoraClient } from './client.js';
3
- export { createKitKoraClient, type KoraKitClient } from './kit/index.js';
4
4
  export { koraPlugin, type KoraApi } from './plugin.js';
package/dist/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
+ export * from './error.js';
1
2
  export * from './types/index.js';
2
3
  export { KoraClient } from './client.js';
3
- export { createKitKoraClient } from './kit/index.js';
4
4
  export { koraPlugin } from './plugin.js';
@@ -1,7 +1,7 @@
1
1
  import { type Address, type TransactionMessage, type TransactionMessageWithFeePayer, type TransactionSigner } from '@solana/kit';
2
2
  import { KoraClient } from '../client.js';
3
- import type { KoraKitClientConfig } from '../types/index.js';
4
- export declare function createKoraTransactionPlanExecutor(koraClient: KoraClient, config: KoraKitClientConfig, payerSigner: TransactionSigner, payment: {
3
+ import type { KoraBundleConfig } from '../types/index.js';
4
+ export declare function createKoraTransactionPlanExecutor(koraClient: KoraClient, config: KoraBundleConfig, userSigner: TransactionSigner, payerSigner: TransactionSigner, payment: {
5
5
  destinationTokenAccount: Address;
6
6
  sourceTokenAccount: Address;
7
7
  } | undefined, resolveProvisoryComputeUnitLimit: (<T extends TransactionMessage & TransactionMessageWithFeePayer>(transactionMessage: T) => Promise<T>) | undefined): import("@solana/kit").TransactionPlanExecutor<import("@solana/kit").TransactionPlanResultContext>;
@@ -5,7 +5,7 @@ import { removePaymentInstruction, updatePaymentInstructionAmount } from './paym
5
5
  // submitting each one individually via `signAndSendTransaction`. This would let users
6
6
  // compose Jito bundles through the Kit plan/execute pipeline rather than manually encoding
7
7
  // transactions and calling `client.kora.signAndSendBundle()`.
8
- export function createKoraTransactionPlanExecutor(koraClient, config, payerSigner, payment, resolveProvisoryComputeUnitLimit) {
8
+ export function createKoraTransactionPlanExecutor(koraClient, config, userSigner, payerSigner, payment, resolveProvisoryComputeUnitLimit) {
9
9
  return createTransactionPlanExecutor({
10
10
  async executeTransactionMessage(_context, transactionMessage) {
11
11
  // Kora manages blockhash validity; set max height to avoid premature client-side expiry checks
@@ -42,8 +42,8 @@ export function createKoraTransactionPlanExecutor(koraClient, config, payerSigne
42
42
  }
43
43
  // Replace placeholder with real fee amount, or strip it if fee is 0
44
44
  const finalIxs = feeInToken > 0
45
- ? updatePaymentInstructionAmount(currentIxs, config.feePayerWallet, sourceTokenAccount, destinationTokenAccount, feeInToken, config.tokenProgramId)
46
- : removePaymentInstruction(currentIxs, sourceTokenAccount, destinationTokenAccount, config.feePayerWallet, config.tokenProgramId);
45
+ ? updatePaymentInstructionAmount(currentIxs, userSigner, sourceTokenAccount, destinationTokenAccount, feeInToken, config.tokenProgramId)
46
+ : removePaymentInstruction(currentIxs, sourceTokenAccount, destinationTokenAccount, userSigner, config.tokenProgramId);
47
47
  const resolvedMsg = pipe(createTransactionMessage({ version: 0 }), m => setTransactionMessageFeePayerSigner(payerSigner, m), m => setTransactionMessageLifetimeUsingBlockhash({
48
48
  blockhash: blockhash(bh),
49
49
  lastValidBlockHeight: BigInt(Number.MAX_SAFE_INTEGER),
@@ -1,32 +1,39 @@
1
- import type { KoraKitClientConfig } from '../types/index.js';
2
- /** The type returned by {@link createKitKoraClient}. */
3
- export type KoraKitClient = Awaited<ReturnType<typeof createKitKoraClient>>;
1
+ import { type ClientWithIdentity } from '@solana/kit';
2
+ import type { KoraBundleConfig, KoraKitClientConfig } from '../types/index.js';
4
3
  /**
5
- * Creates a Kora Kit client composed from Kit plugins.
4
+ * Kora bundle plugin.
5
+ *
6
+ * Composes RPC + payer + Kora RPC methods + transaction planning/execution into a single
7
+ * plugin that mirrors the shape of `solanaRpc` from `@solana/kit-plugin-rpc`.
6
8
  *
7
- * The returned client satisfies `ClientWithPayer`, `ClientWithTransactionPlanning`,
8
- * and `ClientWithTransactionSending`, making it composable with Kit program plugins.
9
+ * The caller's signer is read from `client.identity` install `identity()` (or `signer()`)
10
+ * from `@solana/kit-plugin-signer` before this plugin. The fee payer (Kora's address) is
11
+ * fetched from the Kora node and installed internally as a noop signer, overriding any
12
+ * `payer` already on the client.
9
13
  *
10
14
  * @beta This API is experimental and may change in future releases.
11
15
  *
12
16
  * @example
13
- * ```typescript
14
- * import { createKitKoraClient } from '@solana/kora';
15
- * import { address } from '@solana/kit';
16
- *
17
- * const client = await createKitKoraClient({
18
- * endpoint: 'https://kora.example.com',
19
- * rpcUrl: 'https://api.mainnet-beta.solana.com',
20
- * feeToken: address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
21
- * feePayerWallet: userSigner,
22
- * });
23
- *
24
- * const result = await client.sendTransaction([myInstruction]);
17
+ * ```ts
18
+ * import { createClient } from '@solana/kit';
19
+ * import { identity } from '@solana/kit-plugin-signer';
20
+ * import { kora } from '@solana/kora';
21
+ *
22
+ * const client = await createClient()
23
+ * .use(identity(userSigner))
24
+ * .use(kora({
25
+ * endpoint: 'https://kora.example.com',
26
+ * rpcUrl: 'https://api.mainnet-beta.solana.com',
27
+ * feeToken: address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
28
+ * }));
29
+ *
30
+ * const signature = await client.sendTransaction([myInstruction]);
25
31
  * ```
26
32
  */
27
- export declare function createKitKoraClient(config: KoraKitClientConfig): Promise<import("@solana/kit").Client<import("@solana/kit").ClientWithTransactionPlanning & import("@solana/kit").ClientWithTransactionSending & object & {
33
+ export declare function kora(config: KoraBundleConfig): <T extends ClientWithIdentity>(client: T) => Promise<Omit<Omit<Omit<Omit<Omit<T, "payer"> & {
34
+ payer: import("@solana/kit").NoopSigner<string>;
35
+ }, "rpc"> & {
28
36
  rpc: import("@solana/kit").Rpc<import("@solana/kit").RequestAirdropApi & import("@solana/kit").GetAccountInfoApi & import("@solana/kit").GetBalanceApi & import("@solana/kit").GetBlockApi & import("@solana/kit").GetBlockCommitmentApi & import("@solana/kit").GetBlockHeightApi & import("@solana/kit").GetBlockProductionApi & import("@solana/kit").GetBlocksApi & import("@solana/kit").GetBlocksWithLimitApi & import("@solana/kit").GetBlockTimeApi & import("@solana/kit").GetClusterNodesApi & import("@solana/kit").GetEpochInfoApi & import("@solana/kit").GetEpochScheduleApi & import("@solana/kit").GetFeeForMessageApi & import("@solana/kit").GetFirstAvailableBlockApi & import("@solana/kit").GetGenesisHashApi & import("@solana/kit").GetHealthApi & import("@solana/kit").GetHighestSnapshotSlotApi & import("@solana/kit").GetIdentityApi & import("@solana/kit").GetInflationGovernorApi & import("@solana/kit").GetInflationRateApi & import("@solana/kit").GetInflationRewardApi & import("@solana/kit").GetLargestAccountsApi & import("@solana/kit").GetLatestBlockhashApi & import("@solana/kit").GetLeaderScheduleApi & import("@solana/kit").GetMaxRetransmitSlotApi & import("@solana/kit").GetMaxShredInsertSlotApi & import("@solana/kit").GetMinimumBalanceForRentExemptionApi & import("@solana/kit").GetMultipleAccountsApi & import("@solana/kit").GetProgramAccountsApi & import("@solana/kit").GetRecentPerformanceSamplesApi & import("@solana/kit").GetRecentPrioritizationFeesApi & import("@solana/kit").GetSignaturesForAddressApi & import("@solana/kit").GetSignatureStatusesApi & import("@solana/kit").GetSlotApi & import("@solana/kit").GetSlotLeaderApi & import("@solana/kit").GetSlotLeadersApi & import("@solana/kit").GetStakeMinimumDelegationApi & import("@solana/kit").GetSupplyApi & import("@solana/kit").GetTokenAccountBalanceApi & import("@solana/kit").GetTokenAccountsByDelegateApi & import("@solana/kit").GetTokenAccountsByOwnerApi & import("@solana/kit").GetTokenLargestAccountsApi & import("@solana/kit").GetTokenSupplyApi & import("@solana/kit").GetTransactionApi & import("@solana/kit").GetTransactionCountApi & import("@solana/kit").GetVersionApi & import("@solana/kit").GetVoteAccountsApi & import("@solana/kit").IsBlockhashValidApi & import("@solana/kit").MinimumLedgerSlotApi & import("@solana/kit").SendTransactionApi & import("@solana/kit").SimulateTransactionApi>;
29
- rpcSubscriptions: import("@solana/kit").RpcSubscriptions<import("@solana/kit").SolanaRpcSubscriptionsApi>;
30
37
  } & {
31
38
  kora: {
32
39
  estimateBundleFee(request: import("../types/index.js").EstimateBundleFeeRequest): Promise<import("../types/index.js").KitEstimateBundleFeeResponse>;
@@ -42,12 +49,62 @@ export declare function createKitKoraClient(config: KoraKitClientConfig): Promis
42
49
  signBundle(request: import("../types/index.js").SignBundleRequest): Promise<import("../types/index.js").KitSignBundleResponse>;
43
50
  signTransaction(request: import("../types/index.js").SignTransactionRequest): Promise<import("../types/index.js").KitSignTransactionResponse>;
44
51
  };
45
- } & {
46
- payer: import("@solana/kit").TransactionSigner;
47
52
  } & {
48
53
  paymentAddress: import("@solana/kit").Address | undefined;
49
- } & {
54
+ }, "transactionPlanner"> & {
50
55
  transactionPlanner: import("@solana/kit").TransactionPlanner;
56
+ }, "transactionPlanExecutor"> & {
57
+ transactionPlanExecutor: import("@solana/kit").TransactionPlanExecutor;
58
+ }, keyof import("@solana/kit").ClientWithTransactionPlanning | keyof import("@solana/kit").ClientWithTransactionSending> & import("@solana/kit").ClientWithTransactionPlanning & import("@solana/kit").ClientWithTransactionSending>;
59
+ /**
60
+ * The type returned by {@link createKitKoraClient}.
61
+ *
62
+ * @deprecated Derive the client type from your own `createClient().use(...).use(kora(...))`
63
+ * composition instead. Will be removed in 0.4.0.
64
+ */
65
+ export type KoraKitClient = Awaited<ReturnType<typeof createKitKoraClient>>;
66
+ /**
67
+ * Creates a Kora Kit client composed from Kit plugins.
68
+ *
69
+ * @beta This API is experimental and may change in future releases.
70
+ *
71
+ * @deprecated Compose with the {@link kora} plugin instead:
72
+ * ```ts
73
+ * import { createClient } from '@solana/kit';
74
+ * import { identity } from '@solana/kit-plugin-signer';
75
+ * import { kora } from '@solana/kora';
76
+ *
77
+ * const client = await createClient()
78
+ * .use(identity(userSigner))
79
+ * .use(kora({ endpoint, rpcUrl, feeToken }));
80
+ * ```
81
+ * Will be removed in 0.4.0.
82
+ */
83
+ export declare function createKitKoraClient(config: KoraKitClientConfig): Promise<import("@solana/kit").Client<Omit<Omit<Omit<Omit<Omit<Omit<object, "identity"> & {
84
+ identity: import("@solana/kit").TransactionSigner;
85
+ }, "payer"> & {
86
+ payer: import("@solana/kit").NoopSigner<string>;
87
+ }, "rpc"> & {
88
+ rpc: import("@solana/kit").Rpc<import("@solana/kit").RequestAirdropApi & import("@solana/kit").GetAccountInfoApi & import("@solana/kit").GetBalanceApi & import("@solana/kit").GetBlockApi & import("@solana/kit").GetBlockCommitmentApi & import("@solana/kit").GetBlockHeightApi & import("@solana/kit").GetBlockProductionApi & import("@solana/kit").GetBlocksApi & import("@solana/kit").GetBlocksWithLimitApi & import("@solana/kit").GetBlockTimeApi & import("@solana/kit").GetClusterNodesApi & import("@solana/kit").GetEpochInfoApi & import("@solana/kit").GetEpochScheduleApi & import("@solana/kit").GetFeeForMessageApi & import("@solana/kit").GetFirstAvailableBlockApi & import("@solana/kit").GetGenesisHashApi & import("@solana/kit").GetHealthApi & import("@solana/kit").GetHighestSnapshotSlotApi & import("@solana/kit").GetIdentityApi & import("@solana/kit").GetInflationGovernorApi & import("@solana/kit").GetInflationRateApi & import("@solana/kit").GetInflationRewardApi & import("@solana/kit").GetLargestAccountsApi & import("@solana/kit").GetLatestBlockhashApi & import("@solana/kit").GetLeaderScheduleApi & import("@solana/kit").GetMaxRetransmitSlotApi & import("@solana/kit").GetMaxShredInsertSlotApi & import("@solana/kit").GetMinimumBalanceForRentExemptionApi & import("@solana/kit").GetMultipleAccountsApi & import("@solana/kit").GetProgramAccountsApi & import("@solana/kit").GetRecentPerformanceSamplesApi & import("@solana/kit").GetRecentPrioritizationFeesApi & import("@solana/kit").GetSignaturesForAddressApi & import("@solana/kit").GetSignatureStatusesApi & import("@solana/kit").GetSlotApi & import("@solana/kit").GetSlotLeaderApi & import("@solana/kit").GetSlotLeadersApi & import("@solana/kit").GetStakeMinimumDelegationApi & import("@solana/kit").GetSupplyApi & import("@solana/kit").GetTokenAccountBalanceApi & import("@solana/kit").GetTokenAccountsByDelegateApi & import("@solana/kit").GetTokenAccountsByOwnerApi & import("@solana/kit").GetTokenLargestAccountsApi & import("@solana/kit").GetTokenSupplyApi & import("@solana/kit").GetTransactionApi & import("@solana/kit").GetTransactionCountApi & import("@solana/kit").GetVersionApi & import("@solana/kit").GetVoteAccountsApi & import("@solana/kit").IsBlockhashValidApi & import("@solana/kit").MinimumLedgerSlotApi & import("@solana/kit").SendTransactionApi & import("@solana/kit").SimulateTransactionApi>;
51
89
  } & {
90
+ kora: {
91
+ estimateBundleFee(request: import("../types/index.js").EstimateBundleFeeRequest): Promise<import("../types/index.js").KitEstimateBundleFeeResponse>;
92
+ estimateTransactionFee(request: import("../types/index.js").EstimateTransactionFeeRequest): Promise<import("../types/index.js").KitEstimateFeeResponse>;
93
+ getBlockhash(): Promise<import("../types/index.js").KitBlockhashResponse>;
94
+ getConfig(): Promise<import("../types/index.js").KitConfigResponse>;
95
+ getPayerSigner(): Promise<import("../types/index.js").KitPayerSignerResponse>;
96
+ getPaymentInstruction(request: import("../types/index.js").GetPaymentInstructionRequest): Promise<import("../types/index.js").KitPaymentInstructionResponse>;
97
+ getSupportedTokens(): Promise<import("../types/index.js").KitSupportedTokensResponse>;
98
+ getVersion(): Promise<import("../types/index.js").GetVersionResponse>;
99
+ signAndSendBundle(request: import("../types/index.js").SignAndSendBundleRequest): Promise<import("../types/index.js").KitSignAndSendBundleResponse>;
100
+ signAndSendTransaction(request: import("../types/index.js").SignAndSendTransactionRequest): Promise<import("../types/index.js").KitSignAndSendTransactionResponse>;
101
+ signBundle(request: import("../types/index.js").SignBundleRequest): Promise<import("../types/index.js").KitSignBundleResponse>;
102
+ signTransaction(request: import("../types/index.js").SignTransactionRequest): Promise<import("../types/index.js").KitSignTransactionResponse>;
103
+ };
104
+ } & {
105
+ paymentAddress: import("@solana/kit").Address | undefined;
106
+ }, "transactionPlanner"> & {
107
+ transactionPlanner: import("@solana/kit").TransactionPlanner;
108
+ }, "transactionPlanExecutor"> & {
52
109
  transactionPlanExecutor: import("@solana/kit").TransactionPlanExecutor;
53
- }>>;
110
+ }, keyof import("@solana/kit").ClientWithTransactionPlanning | keyof import("@solana/kit").ClientWithTransactionSending> & import("@solana/kit").ClientWithTransactionPlanning & import("@solana/kit").ClientWithTransactionSending>>;
@@ -1,7 +1,7 @@
1
- import { address, createEmptyClient, createNoopSigner, createSolanaRpc } from '@solana/kit';
1
+ import { address, createClient, createNoopSigner, createSolanaRpc, pipe } from '@solana/kit';
2
2
  import { planAndSendTransactions, transactionPlanExecutor as transactionPlanExecutorPlugin, transactionPlanner as transactionPlannerPlugin, } from '@solana/kit-plugin-instruction-plan';
3
- import { payer } from '@solana/kit-plugin-payer';
4
- import { rpc } from '@solana/kit-plugin-rpc';
3
+ import { solanaRpcConnection } from '@solana/kit-plugin-rpc';
4
+ import { identity, payer } from '@solana/kit-plugin-signer';
5
5
  import { estimateAndUpdateProvisoryComputeUnitLimitFactory, estimateComputeUnitLimitFactory, } from '@solana-program/compute-budget';
6
6
  import { KoraClient } from '../client.js';
7
7
  import { koraPlugin } from '../plugin.js';
@@ -9,67 +9,89 @@ import { createKoraTransactionPlanExecutor } from './executor.js';
9
9
  import { buildPlaceholderPaymentInstruction, koraPaymentAddress } from './payment.js';
10
10
  import { buildComputeBudgetInstructions, createKoraTransactionPlanner } from './planner.js';
11
11
  /**
12
- * Creates a Kora Kit client composed from Kit plugins.
12
+ * Kora bundle plugin.
13
+ *
14
+ * Composes RPC + payer + Kora RPC methods + transaction planning/execution into a single
15
+ * plugin that mirrors the shape of `solanaRpc` from `@solana/kit-plugin-rpc`.
13
16
  *
14
- * The returned client satisfies `ClientWithPayer`, `ClientWithTransactionPlanning`,
15
- * and `ClientWithTransactionSending`, making it composable with Kit program plugins.
17
+ * The caller's signer is read from `client.identity` install `identity()` (or `signer()`)
18
+ * from `@solana/kit-plugin-signer` before this plugin. The fee payer (Kora's address) is
19
+ * fetched from the Kora node and installed internally as a noop signer, overriding any
20
+ * `payer` already on the client.
16
21
  *
17
22
  * @beta This API is experimental and may change in future releases.
18
23
  *
19
24
  * @example
20
- * ```typescript
21
- * import { createKitKoraClient } from '@solana/kora';
22
- * import { address } from '@solana/kit';
25
+ * ```ts
26
+ * import { createClient } from '@solana/kit';
27
+ * import { identity } from '@solana/kit-plugin-signer';
28
+ * import { kora } from '@solana/kora';
29
+ *
30
+ * const client = await createClient()
31
+ * .use(identity(userSigner))
32
+ * .use(kora({
33
+ * endpoint: 'https://kora.example.com',
34
+ * rpcUrl: 'https://api.mainnet-beta.solana.com',
35
+ * feeToken: address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
36
+ * }));
37
+ *
38
+ * const signature = await client.sendTransaction([myInstruction]);
39
+ * ```
40
+ */
41
+ export function kora(config) {
42
+ return async (client) => {
43
+ const koraClient = new KoraClient({
44
+ apiKey: config.apiKey,
45
+ getRecaptchaToken: config.getRecaptchaToken,
46
+ hmacSecret: config.hmacSecret,
47
+ rpcUrl: config.endpoint,
48
+ });
49
+ const { signer_address, payment_address } = await koraClient.getPayerSigner();
50
+ const paymentAddr = payment_address ? address(payment_address) : undefined;
51
+ const payerSigner = createNoopSigner(address(signer_address));
52
+ const computeBudgetIxs = buildComputeBudgetInstructions(config);
53
+ const solanaRpc = createSolanaRpc(config.rpcUrl);
54
+ const hasCuEstimation = config.computeUnitLimit === undefined;
55
+ const resolveProvisoryComputeUnitLimit = hasCuEstimation
56
+ ? estimateAndUpdateProvisoryComputeUnitLimitFactory(estimateComputeUnitLimitFactory({ rpc: solanaRpc }))
57
+ : undefined;
58
+ const userSigner = client.identity;
59
+ const payment = paymentAddr
60
+ ? await buildPlaceholderPaymentInstruction(userSigner, paymentAddr, config.feeToken, config.tokenProgramId)
61
+ : undefined;
62
+ const koraTransactionPlanner = createKoraTransactionPlanner(payerSigner, computeBudgetIxs, payment?.instruction, hasCuEstimation);
63
+ const koraTransactionPlanExecutor = createKoraTransactionPlanExecutor(koraClient, config, userSigner, payerSigner, payment
64
+ ? {
65
+ destinationTokenAccount: payment.destinationTokenAccount,
66
+ sourceTokenAccount: payment.sourceTokenAccount,
67
+ }
68
+ : undefined, resolveProvisoryComputeUnitLimit);
69
+ return pipe(client, payer(payerSigner), solanaRpcConnection(config.rpcUrl), koraPlugin({
70
+ endpoint: config.endpoint,
71
+ koraClient,
72
+ }), koraPaymentAddress(paymentAddr), transactionPlannerPlugin(koraTransactionPlanner), transactionPlanExecutorPlugin(koraTransactionPlanExecutor), planAndSendTransactions());
73
+ };
74
+ }
75
+ /**
76
+ * Creates a Kora Kit client composed from Kit plugins.
77
+ *
78
+ * @beta This API is experimental and may change in future releases.
23
79
  *
24
- * const client = await createKitKoraClient({
25
- * endpoint: 'https://kora.example.com',
26
- * rpcUrl: 'https://api.mainnet-beta.solana.com',
27
- * feeToken: address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
28
- * feePayerWallet: userSigner,
29
- * });
80
+ * @deprecated Compose with the {@link kora} plugin instead:
81
+ * ```ts
82
+ * import { createClient } from '@solana/kit';
83
+ * import { identity } from '@solana/kit-plugin-signer';
84
+ * import { kora } from '@solana/kora';
30
85
  *
31
- * const result = await client.sendTransaction([myInstruction]);
86
+ * const client = await createClient()
87
+ * .use(identity(userSigner))
88
+ * .use(kora({ endpoint, rpcUrl, feeToken }));
32
89
  * ```
90
+ * Will be removed in 0.4.0.
33
91
  */
34
92
  // TODO: Bundle support — the plan/execute pipeline currently handles single transactions only.
35
93
  // For Jito bundles, users must manually encode transactions and call `client.kora.signAndSendBundle()`.
36
- // A future `createKitKoraBundleClient` (or a bundle-aware executor plugin) could extend this to
37
- // plan multiple transaction messages and submit them as a single bundle.
38
94
  export async function createKitKoraClient(config) {
39
- const koraClient = new KoraClient({
40
- apiKey: config.apiKey,
41
- getRecaptchaToken: config.getRecaptchaToken,
42
- hmacSecret: config.hmacSecret,
43
- rpcUrl: config.endpoint,
44
- });
45
- const { signer_address, payment_address } = await koraClient.getPayerSigner();
46
- const paymentAddr = payment_address ? address(payment_address) : undefined;
47
- const payerSigner = createNoopSigner(address(signer_address));
48
- const computeBudgetIxs = buildComputeBudgetInstructions(config);
49
- const solanaRpc = createSolanaRpc(config.rpcUrl);
50
- const hasCuEstimation = config.computeUnitLimit === undefined;
51
- const resolveProvisoryComputeUnitLimit = hasCuEstimation
52
- ? estimateAndUpdateProvisoryComputeUnitLimitFactory(estimateComputeUnitLimitFactory({ rpc: solanaRpc }))
53
- : undefined;
54
- const payment = paymentAddr
55
- ? await buildPlaceholderPaymentInstruction(config.feePayerWallet, paymentAddr, config.feeToken, config.tokenProgramId)
56
- : undefined;
57
- const koraTransactionPlanner = createKoraTransactionPlanner(payerSigner, computeBudgetIxs, payment?.instruction, hasCuEstimation);
58
- const koraTransactionPlanExecutor = createKoraTransactionPlanExecutor(koraClient, config, payerSigner, payment
59
- ? {
60
- destinationTokenAccount: payment.destinationTokenAccount,
61
- sourceTokenAccount: payment.sourceTokenAccount,
62
- }
63
- : undefined, resolveProvisoryComputeUnitLimit);
64
- return createEmptyClient()
65
- .use(rpc(config.rpcUrl))
66
- .use(koraPlugin({
67
- endpoint: config.endpoint,
68
- koraClient,
69
- }))
70
- .use(payer(payerSigner))
71
- .use(koraPaymentAddress(paymentAddr))
72
- .use(transactionPlannerPlugin(koraTransactionPlanner))
73
- .use(transactionPlanExecutorPlugin(koraTransactionPlanExecutor))
74
- .use(planAndSendTransactions());
95
+ const { feePayerWallet, ...bundleConfig } = config;
96
+ return await createClient().use(identity(feePayerWallet)).use(kora(bundleConfig));
75
97
  }
@@ -1,4 +1,4 @@
1
1
  import { type Instruction, type TransactionSigner } from '@solana/kit';
2
- import type { KoraKitClientConfig } from '../types/index.js';
3
- export declare function buildComputeBudgetInstructions(config: KoraKitClientConfig): Instruction[];
2
+ import type { KoraBundleConfig } from '../types/index.js';
3
+ export declare function buildComputeBudgetInstructions(config: KoraBundleConfig): Instruction[];
4
4
  export declare function createKoraTransactionPlanner(payerSigner: TransactionSigner, computeBudgetIxs: Instruction[], paymentInstruction: Instruction | undefined, hasCuEstimation: boolean): import("@solana/kit").TransactionPlanner;
@@ -4,7 +4,7 @@ import type { EstimateBundleFeeRequest, EstimateTransactionFeeRequest, GetPaymen
4
4
  *
5
5
  * The plugin exposes all Kora RPC methods with Kit-typed responses (Address, Blockhash).
6
6
  *
7
- * **Note:** The plugin pattern with `createEmptyClient().use()` requires `@solana/kit` v5.4.0+.
7
+ * **Note:** The plugin pattern with `createClient().use()` requires `@solana/kit` v6.8.0+.
8
8
  * For older kit versions, use `KoraClient` directly instead.
9
9
  *
10
10
  * @param config - Plugin configuration
@@ -15,10 +15,10 @@ import type { EstimateBundleFeeRequest, EstimateTransactionFeeRequest, GetPaymen
15
15
  *
16
16
  * @example
17
17
  * ```typescript
18
- * import { createEmptyClient } from '@solana/kit';
18
+ * import { createClient } from '@solana/kit';
19
19
  * import { koraPlugin } from '@solana/kora';
20
20
  *
21
- * const client = createEmptyClient()
21
+ * const client = createClient()
22
22
  * .use(koraPlugin({ endpoint: 'https://kora.example.com' }));
23
23
  *
24
24
  * // All responses have Kit-typed fields
@@ -5,7 +5,7 @@ import { KoraClient } from './client.js';
5
5
  *
6
6
  * The plugin exposes all Kora RPC methods with Kit-typed responses (Address, Blockhash).
7
7
  *
8
- * **Note:** The plugin pattern with `createEmptyClient().use()` requires `@solana/kit` v5.4.0+.
8
+ * **Note:** The plugin pattern with `createClient().use()` requires `@solana/kit` v6.8.0+.
9
9
  * For older kit versions, use `KoraClient` directly instead.
10
10
  *
11
11
  * @param config - Plugin configuration
@@ -16,10 +16,10 @@ import { KoraClient } from './client.js';
16
16
  *
17
17
  * @example
18
18
  * ```typescript
19
- * import { createEmptyClient } from '@solana/kit';
19
+ * import { createClient } from '@solana/kit';
20
20
  * import { koraPlugin } from '@solana/kora';
21
21
  *
22
- * const client = createEmptyClient()
22
+ * const client = createClient()
23
23
  * .use(koraPlugin({ endpoint: 'https://kora.example.com' }));
24
24
  *
25
25
  * // All responses have Kit-typed fields
@@ -84,7 +84,9 @@ export function koraPlugin(config) {
84
84
  fee_payers: result.fee_payers.map(addr => address(addr)),
85
85
  validation_config: {
86
86
  ...result.validation_config,
87
- allowed_programs: result.validation_config.allowed_programs.map(addr => address(addr)),
87
+ allowed_programs: Array.isArray(result.validation_config.allowed_programs)
88
+ ? result.validation_config.allowed_programs.map(addr => address(addr))
89
+ : result.validation_config.allowed_programs,
88
90
  allowed_spl_paid_tokens: result.validation_config.allowed_spl_paid_tokens.map(addr => address(addr)),
89
91
  allowed_tokens: result.validation_config.allowed_tokens.map(addr => address(addr)),
90
92
  disallowed_accounts: result.validation_config.disallowed_accounts.map(addr => address(addr)),
@@ -15,10 +15,20 @@ export interface SignTransactionRequest {
15
15
  /** Optional user ID for usage tracking (required when pricing is free and usage tracking is enabled) */
16
16
  user_id?: string;
17
17
  }
18
+ /**
19
+ * The transaction lifecycle milestone the server waits for before responding,
20
+ * ordered by increasing assurance: `signed` < `sent` < `confirmed`:
21
+ * - `confirmed`: wait for on-chain confirmation (default)
22
+ * - `sent`: return once the RPC node accepts the transaction
23
+ * - `signed`: return as soon as signing completes and broadcast in the background
24
+ */
25
+ export type RespondAfter = 'confirmed' | 'sent' | 'signed';
18
26
  /**
19
27
  * Parameters for signing and sending a transaction.
20
28
  */
21
29
  export interface SignAndSendTransactionRequest {
30
+ /** Optional milestone to wait for before responding (defaults to "confirmed") */
31
+ respond_after?: RespondAfter;
22
32
  /** Optional signer verification during transaction simulation (defaults to false) */
23
33
  sig_verify?: boolean;
24
34
  /** Optional signer address for the transaction */
@@ -234,8 +244,8 @@ export type PriceSource = 'Jupiter' | 'Mock';
234
244
  * Validation configuration for the Kora server.
235
245
  */
236
246
  export interface ValidationConfig {
237
- /** List of allowed Solana program IDs */
238
- allowed_programs: string[];
247
+ /** List of allowed Solana program IDs, or "All" to allow any program. */
248
+ allowed_programs: 'All' | string[];
239
249
  /** List of SPL tokens accepted for paid transactions */
240
250
  allowed_spl_paid_tokens: string[];
241
251
  /** List of allowed token mint addresses for fee payment */
@@ -430,6 +440,10 @@ export interface FeePayerPolicy {
430
440
  export interface RpcError {
431
441
  /** Error code */
432
442
  code: number;
443
+ /** Optional structured data about the error */
444
+ data?: {
445
+ error_type: string;
446
+ };
433
447
  /** Human-readable error message */
434
448
  message: string;
435
449
  }
@@ -598,8 +612,8 @@ export interface KitSignAndSendBundleResponse {
598
612
  }
599
613
  /** Plugin validation config with Kit Address types */
600
614
  export interface KitValidationConfig {
601
- /** List of allowed Solana program IDs */
602
- allowed_programs: Address[];
615
+ /** List of allowed Solana program IDs, or "All" to allow any program. */
616
+ allowed_programs: 'All' | Address[];
603
617
  /** List of SPL tokens accepted for paid transactions */
604
618
  allowed_spl_paid_tokens: Address[];
605
619
  /** List of allowed token mint addresses for fee payment */
@@ -622,13 +636,17 @@ export interface KitValidationConfig {
622
636
  /**
623
637
  * Kit Client Types
624
638
  */
625
- /** Configuration for {@link createKitKoraClient}. */
626
- export interface KoraKitClientConfig {
639
+ /**
640
+ * Configuration for the {@link kora} bundle plugin.
641
+ *
642
+ * The user's signer is read from `client.identity` (set via `identity()` from
643
+ * `@solana/kit-plugin-signer`), so this config intentionally omits any signer field.
644
+ */
645
+ export interface KoraBundleConfig {
627
646
  readonly apiKey?: string;
628
647
  readonly computeUnitLimit?: number;
629
648
  readonly computeUnitPrice?: MicroLamports;
630
649
  readonly endpoint: string;
631
- readonly feePayerWallet: TransactionSigner;
632
650
  readonly feeToken: Address;
633
651
  readonly getRecaptchaToken?: () => Promise<string> | string;
634
652
  readonly hmacSecret?: string;
@@ -636,3 +654,11 @@ export interface KoraKitClientConfig {
636
654
  readonly tokenProgramId?: Address;
637
655
  readonly userId?: string;
638
656
  }
657
+ /**
658
+ * Configuration for {@link createKitKoraClient}.
659
+ *
660
+ * @deprecated Use {@link KoraBundleConfig} with the {@link kora} plugin instead.
661
+ */
662
+ export interface KoraKitClientConfig extends KoraBundleConfig {
663
+ readonly feePayerWallet: TransactionSigner;
664
+ }
@@ -1,7 +1,8 @@
1
- import { address, appendTransactionMessageInstruction, compileTransaction, createTransactionMessage, getBase64Decoder, getBase64EncodedWireTransaction, getBase64Encoder, getTransactionDecoder, getTransactionEncoder, partiallySignTransaction, pipe, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, } from '@solana/kit';
1
+ import { address, appendTransactionMessageInstruction, compileTransaction, createClient, createTransactionMessage, getBase64Decoder, getBase64EncodedWireTransaction, getBase64Encoder, getTransactionDecoder, getTransactionEncoder, partiallySignTransaction, pipe, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, } from '@solana/kit';
2
+ import { identity } from '@solana/kit-plugin-signer';
2
3
  import { getTransferSolInstruction } from '@solana-program/system';
3
- import { findAssociatedTokenPda, getTransferInstruction, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
4
- import { createKitKoraClient } from '../src/index.js';
4
+ import { findAssociatedTokenPda, getTransferInstruction, TOKEN_PROGRAM_ADDRESS, tokenProgram, } from '@solana-program/token';
5
+ import { kora } from '../src/kit/index.js';
5
6
  import { runAuthenticationTests } from './auth-setup.js';
6
7
  import setupTestSuite from './setup.js';
7
8
  function transactionFromBase64(base64) {
@@ -215,7 +216,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
215
216
  expect(signResult).toBeDefined();
216
217
  expect(signResult.signed_transaction).toBeDefined();
217
218
  expect(signResult.signature).toBeDefined();
218
- });
219
+ }, 30000);
219
220
  it('should get payment instruction', async () => {
220
221
  const { transaction } = await buildTokenTransferTransaction({
221
222
  amount: 1000000n,
@@ -319,7 +320,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
319
320
  expect(result.signer_pubkey).toBeDefined();
320
321
  expect(result.bundle_uuid).toBeDefined();
321
322
  expect(typeof result.bundle_uuid).toBe('string');
322
- });
323
+ }, 30000);
323
324
  });
324
325
  describe('Error Handling', () => {
325
326
  it('should handle invalid transaction for signing', async () => {
@@ -334,14 +335,18 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
334
335
  if (FREE_PRICING) {
335
336
  describe('Kit Client (free pricing)', () => {
336
337
  let freeClient;
337
- beforeAll(async () => {
338
+ async function buildFreeClient() {
338
339
  const koraRpcUrl = process.env.KORA_RPC_URL || 'http://127.0.0.1:8080';
339
- freeClient = await createKitKoraClient({
340
+ return createClient()
341
+ .use(identity(testWallet))
342
+ .use(kora({
340
343
  endpoint: koraRpcUrl,
341
344
  rpcUrl: process.env.SOLANA_RPC_URL || 'http://127.0.0.1:8899',
342
345
  feeToken: usdcMint,
343
- feePayerWallet: testWallet,
344
- });
346
+ }));
347
+ }
348
+ beforeAll(async () => {
349
+ freeClient = await buildFreeClient();
345
350
  }, 30000);
346
351
  it('should send transaction without payment instruction when fee is 0', async () => {
347
352
  const ix = getTransferSolInstruction({
@@ -364,4 +369,47 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without
364
369
  }, 30000);
365
370
  });
366
371
  }
372
+ (FREE_PRICING ? describe.skip : describe)('Kit Client (standard pricing)', () => {
373
+ let paidClient;
374
+ async function buildPaidClient() {
375
+ const koraRpcUrl = process.env.KORA_RPC_URL || 'http://127.0.0.1:8080';
376
+ return createClient()
377
+ .use(identity(testWallet))
378
+ .use(kora({
379
+ endpoint: koraRpcUrl,
380
+ rpcUrl: process.env.SOLANA_RPC_URL || 'http://127.0.0.1:8899',
381
+ feeToken: usdcMint,
382
+ ...(AUTH_ENABLED && {
383
+ apiKey: process.env.KORA_API_KEY || 'test-api-key-123',
384
+ hmacSecret: process.env.KORA_HMAC_SECRET || 'test-hmac-secret-456',
385
+ }),
386
+ }))
387
+ .use(tokenProgram());
388
+ }
389
+ beforeAll(async () => {
390
+ paidClient = await buildPaidClient();
391
+ }, 30000);
392
+ it('should send SPL token transfer with paid fee via kit client', async () => {
393
+ const [sourceAta] = await findAssociatedTokenPda({
394
+ mint: usdcMint,
395
+ owner: testWallet.address,
396
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
397
+ });
398
+ const [destinationAta] = await findAssociatedTokenPda({
399
+ mint: usdcMint,
400
+ owner: koraAddress,
401
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
402
+ });
403
+ const result = await paidClient.token.instructions
404
+ .transfer({
405
+ amount: 1000000n,
406
+ authority: testWallet,
407
+ destination: destinationAta,
408
+ source: sourceAta,
409
+ })
410
+ .sendTransaction();
411
+ expect(result.status).toBe('successful');
412
+ expect(result.context.signature).toBeDefined();
413
+ }, 30000);
414
+ });
367
415
  });
@@ -1,5 +1,6 @@
1
- import { createKitKoraClient } from '../src/kit/index.js';
2
- import { address, createNoopSigner } from '@solana/kit';
1
+ import { createKitKoraClient, kora } from '../src/kit/index.js';
2
+ import { address, createClient, createNoopSigner } from '@solana/kit';
3
+ import { identity, signer } from '@solana/kit-plugin-signer';
3
4
  // Mock fetch globally
4
5
  const mockFetch = jest.fn();
5
6
  global.fetch = mockFetch;
@@ -80,7 +81,7 @@ describe('createKitKoraClient', () => {
80
81
  rpcUrl: MOCK_RPC_URL,
81
82
  feeToken: MOCK_FEE_TOKEN,
82
83
  feePayerWallet: MOCK_WALLET,
83
- })).rejects.toThrow('RPC Error -32000: Server error');
84
+ })).rejects.toThrow('Kora Error -32000: Server error');
84
85
  });
85
86
  it('should expose kora namespace for raw RPC access', async () => {
86
87
  mockRpcResponse({
@@ -489,3 +490,47 @@ describe('createKitKoraClient', () => {
489
490
  });
490
491
  });
491
492
  });
493
+ describe('kora() bundle plugin', () => {
494
+ beforeEach(() => {
495
+ mockFetch.mockClear();
496
+ });
497
+ afterEach(() => {
498
+ jest.resetAllMocks();
499
+ });
500
+ it('composes with identity() and exposes the same surface as the wrapper', async () => {
501
+ mockRpcResponse({
502
+ signer_address: MOCK_PAYER_ADDRESS,
503
+ payment_address: MOCK_PAYMENT_ADDRESS,
504
+ });
505
+ const client = await createClient()
506
+ .use(identity(MOCK_WALLET))
507
+ .use(kora({
508
+ endpoint: MOCK_ENDPOINT,
509
+ rpcUrl: MOCK_RPC_URL,
510
+ feeToken: MOCK_FEE_TOKEN,
511
+ }));
512
+ expect(client.identity.address).toBe(MOCK_WALLET_ADDRESS);
513
+ expect(client.payer.address).toBe(MOCK_PAYER_ADDRESS);
514
+ expect(client.paymentAddress).toBe(MOCK_PAYMENT_ADDRESS);
515
+ expect(typeof client.sendTransaction).toBe('function');
516
+ expect(typeof client.planTransaction).toBe('function');
517
+ expect(client.kora).toBeDefined();
518
+ });
519
+ // TODO: re-enable once @solana/kit supports overriding an already-set field on an
520
+ // extended client.
521
+ it.skip('overrides client.payer when the caller used signer() (sets identity + payer)', async () => {
522
+ mockRpcResponse({
523
+ signer_address: MOCK_PAYER_ADDRESS,
524
+ payment_address: MOCK_PAYMENT_ADDRESS,
525
+ });
526
+ const client = await createClient()
527
+ .use(signer(MOCK_WALLET))
528
+ .use(kora({
529
+ endpoint: MOCK_ENDPOINT,
530
+ rpcUrl: MOCK_RPC_URL,
531
+ feeToken: MOCK_FEE_TOKEN,
532
+ }));
533
+ expect(client.identity.address).toBe(MOCK_WALLET_ADDRESS);
534
+ expect(client.payer.address).toBe(MOCK_PAYER_ADDRESS);
535
+ });
536
+ });
@@ -1,4 +1,4 @@
1
- import { createEmptyClient } from '@solana/kit';
1
+ import { createClient } from '@solana/kit';
2
2
  import { koraPlugin } from '../src/plugin.js';
3
3
  // Mock fetch globally
4
4
  const mockFetch = jest.fn();
@@ -149,6 +149,41 @@ describe('Kora Kit Plugin', () => {
149
149
  expect(result.validation_config.allowed_tokens).toHaveLength(1);
150
150
  expect(result.validation_config.allowed_tokens[0]).toBe('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
151
151
  });
152
+ it('should pass through the "All" wildcard for allowed_programs without crashing', async () => {
153
+ // Regression: ProgramsConfig::All serializes as the string "All" on the wire,
154
+ // which would crash the kit wrapper if it called .map() unconditionally.
155
+ const rawResponse = {
156
+ enabled_methods: {
157
+ estimate_transaction_fee: true,
158
+ get_blockhash: true,
159
+ get_config: true,
160
+ get_supported_tokens: true,
161
+ liveness: true,
162
+ sign_and_send_transaction: true,
163
+ sign_transaction: true,
164
+ transfer_transaction: true,
165
+ },
166
+ fee_payers: ['DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7'],
167
+ validation_config: {
168
+ allowed_programs: 'All',
169
+ allowed_spl_paid_tokens: [],
170
+ allowed_tokens: [],
171
+ disallowed_accounts: [],
172
+ fee_payer_policy: {},
173
+ max_allowed_lamports: 1000000,
174
+ max_signatures: 10,
175
+ price: { margin: 0.1, type: 'margin' },
176
+ price_source: 'Jupiter',
177
+ token2022: {
178
+ blocked_account_extensions: [],
179
+ blocked_mint_extensions: [],
180
+ },
181
+ },
182
+ };
183
+ mockSuccessfulResponse(rawResponse);
184
+ const result = await kora.getConfig();
185
+ expect(result.validation_config.allowed_programs).toBe('All');
186
+ });
152
187
  });
153
188
  describe('getPayerSigner', () => {
154
189
  it('should return Kit-typed Address fields', async () => {
@@ -366,7 +401,7 @@ describe('Kora Kit Plugin', () => {
366
401
  });
367
402
  it('should propagate RPC errors', async () => {
368
403
  mockErrorResponse({ code: -32601, message: 'Method not found' });
369
- await expect(kora.getConfig()).rejects.toThrow('RPC Error -32601: Method not found');
404
+ await expect(kora.getConfig()).rejects.toThrow('Kora Error -32601: Method not found');
370
405
  });
371
406
  it('should propagate network errors', async () => {
372
407
  mockFetch.mockRejectedValueOnce(new Error('Network error'));
@@ -383,14 +418,14 @@ describe('Kora Kit Plugin', () => {
383
418
  expect(api).toBeDefined();
384
419
  });
385
420
  });
386
- describe('createEmptyClient Integration', () => {
421
+ describe('createClient Integration', () => {
387
422
  it('should initialize kora property on Kit client', () => {
388
- const client = createEmptyClient().use(koraPlugin(mockConfig));
423
+ const client = createClient().use(koraPlugin(mockConfig));
389
424
  expect(client).toHaveProperty('kora');
390
425
  expect(client.kora).toBeDefined();
391
426
  });
392
427
  it('should expose all Kora RPC methods', () => {
393
- const client = createEmptyClient().use(koraPlugin(mockConfig));
428
+ const client = createClient().use(koraPlugin(mockConfig));
394
429
  expect(typeof client.kora.getConfig).toBe('function');
395
430
  expect(typeof client.kora.getPayerSigner).toBe('function');
396
431
  expect(typeof client.kora.getBlockhash).toBe('function');
@@ -410,7 +445,7 @@ describe('Kora Kit Plugin', () => {
410
445
  endpoint: mockEndpoint,
411
446
  hmacSecret: 'test-hmac-secret',
412
447
  };
413
- const client = createEmptyClient().use(koraPlugin(authConfig));
448
+ const client = createClient().use(koraPlugin(authConfig));
414
449
  expect(client.kora).toBeDefined();
415
450
  expect(typeof client.kora.getConfig).toBe('function');
416
451
  });
@@ -420,7 +455,7 @@ describe('Kora Kit Plugin', () => {
420
455
  ...c,
421
456
  other: { foo: () => 'bar' },
422
457
  });
423
- const client = createEmptyClient().use(koraPlugin(mockConfig)).use(otherPlugin);
458
+ const client = createClient().use(koraPlugin(mockConfig)).use(otherPlugin);
424
459
  // Both plugins should be available
425
460
  expect(client.kora).toBeDefined();
426
461
  expect(client.other).toBeDefined();
@@ -433,7 +468,7 @@ describe('Kora Kit Plugin', () => {
433
468
  signer_address: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
434
469
  };
435
470
  mockSuccessfulResponse(mockResponse);
436
- const client = createEmptyClient().use(koraPlugin(mockConfig));
471
+ const client = createClient().use(koraPlugin(mockConfig));
437
472
  const result = await client.kora.getPayerSigner();
438
473
  expect(result.signer_address).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
439
474
  expect(result.payment_address).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
@@ -1,10 +1,25 @@
1
- import { assertIsAddress, createKeyPairSignerFromBytes, getBase58Encoder, lamports, } from '@solana/kit';
2
- import { createClient } from '@solana/kit-client-litesvm';
1
+ import { assertIsAddress, createClient, createKeyPairSignerFromBytes, getBase58Encoder, lamports, } from '@solana/kit';
2
+ import { litesvm } from '@solana/kit-plugin-litesvm';
3
+ import { payer } from '@solana/kit-plugin-signer';
3
4
  import { tokenProgram, associatedTokenProgram } from '@solana-program/token';
4
5
  import { config } from 'dotenv';
5
6
  import path from 'path';
6
7
  import { KoraClient } from '../src/index.js';
7
8
  config({ path: path.resolve(process.cwd(), '.env') });
9
+ // Each suite flavor (basic / auth / free) runs concurrently against the same
10
+ // validator. Flavors use distinct wallets (funded by the rust test setup) so
11
+ // they never submit byte-identical transactions, which the validator
12
+ // deduplicates by signature.
13
+ // DO NOT USE THESE KEYPAIRS IN PRODUCTION, TESTING KEYPAIRS ONLY
14
+ const FLAVOR_WALLET_SECRETS = {
15
+ // 7kBPazc3KfccwopUa9dALgeBSXoYjdqtK5UEpXKCAYYH
16
+ auth: '5mDdF4AakwdsEMVKxQ566CNa4bxdqZCEfpWLz9HYWYt1ZjL48HAL8Q3p7ba4nJNUe8QpWrWh5n9kwLKegZm2J7vy',
17
+ // HhA5j2rRiPbMrpF2ZD36r69FyZf3zWmEHRNSZbbNdVjf
18
+ basic: 'tzgfgSWTE3KUA6qfRoFYLaSfJm59uUeZRDy4ybMrLn1JV2drA1mftiaEcVFvq1Lok6h6EX2C4Y9kSKLvQWyMpS5',
19
+ // HrQpAeuWRcajWzJ3FSe5UApiSX2Pp2nNdBDj7eZx4yCo
20
+ free: '4y8NiNNW9fLaaVVDAVxvUCobTXMTpKcZDSdwUidiYXVS8SPVTRg9hGbgUK4Tsp7vrQTK8kw4fJ1LUeHFFnthaFzB',
21
+ };
22
+ const SUITE_FLAVOR = process.env.ENABLE_AUTH === 'true' ? 'auth' : process.env.FREE_PRICING === 'true' ? 'free' : 'basic';
8
23
  const DEFAULTS = {
9
24
  DECIMALS: 6,
10
25
  // Make sure this matches the USDC mint in kora.toml (9BgeTKqmFsPVnfYscfM6NvsgmZxei7XfdciShQ6D3bxJ)
@@ -13,10 +28,8 @@ const DEFAULTS = {
13
28
  KORA_ADDRESS: '7AqpcUvgJ7Kh1VmJZ44rWp2XDow33vswo9VK9VqpPU2d',
14
29
  KORA_RPC_URL: 'http://localhost:8080/',
15
30
  KORA_SIGNER_TYPE: 'memory',
16
- // Make sure this matches the kora-rpc signer address on launch (root .env)
17
- SENDER_SECRET: 'tzgfgSWTE3KUA6qfRoFYLaSfJm59uUeZRDy4ybMrLn1JV2drA1mftiaEcVFvq1Lok6h6EX2C4Y9kSKLvQWyMpS5',
18
31
  SOL_DROP_AMOUNT: 1_000_000_000,
19
- // HhA5j2rRiPbMrpF2ZD36r69FyZf3zWmEHRNSZbbNdVjf
32
+ // 9BgeTKqmFsPVnfYscfM6NvsgmZxei7XfdciShQ6D3bxJ
20
33
  TEST_USDC_MINT_SECRET: '59kKmXphL5UJANqpFFjtH17emEq3oRNmYsx6a3P3vSGJRmhMgVdzH77bkNEi9bArRViT45e8L2TsuPxKNFoc3Qfg',
21
34
  TOKEN_DROP_AMOUNT: 100_000, // Default signer type
22
35
  };
@@ -43,6 +56,12 @@ export function loadEnvironmentVariables() {
43
56
  throw new Error('PRIVY_PUBLIC_KEY must be set when using Privy signer');
44
57
  }
45
58
  break;
59
+ case 'openfort':
60
+ koraAddress = process.env.OPENFORT_PUBLIC_KEY;
61
+ if (!koraAddress) {
62
+ throw new Error('OPENFORT_PUBLIC_KEY must be set when using Openfort signer');
63
+ }
64
+ break;
46
65
  case 'memory':
47
66
  default:
48
67
  koraAddress = DEFAULTS.KORA_ADDRESS;
@@ -53,7 +72,7 @@ export function loadEnvironmentVariables() {
53
72
  const tokenDecimals = Number(process.env.TOKEN_DECIMALS || DEFAULTS.DECIMALS);
54
73
  const tokenDropAmount = Number(process.env.TOKEN_DROP_AMOUNT || DEFAULTS.TOKEN_DROP_AMOUNT);
55
74
  const solDropAmount = BigInt(process.env.SOL_DROP_AMOUNT || DEFAULTS.SOL_DROP_AMOUNT);
56
- const testWalletSecret = process.env.SENDER_SECRET || DEFAULTS.SENDER_SECRET;
75
+ const testWalletSecret = process.env.SENDER_SECRET || FLAVOR_WALLET_SECRETS[SUITE_FLAVOR];
57
76
  const testUsdcMintSecret = process.env.TEST_USDC_MINT_SECRET || DEFAULTS.TEST_USDC_MINT_SECRET;
58
77
  const destinationAddress = process.env.DESTINATION_ADDRESS || DEFAULTS.DESTINATION_ADDRESS;
59
78
  assertIsAddress(destinationAddress);
@@ -89,7 +108,11 @@ async function setupTestSuite() {
89
108
  }
90
109
  : undefined;
91
110
  const { testWallet, usdcMint, destinationAddress } = await createKeyPairSigners();
92
- const client = await createClient({ payer: testWallet }).use(tokenProgram()).use(associatedTokenProgram());
111
+ const client = await createClient()
112
+ .use(payer(testWallet))
113
+ .use(litesvm())
114
+ .use(tokenProgram())
115
+ .use(associatedTokenProgram());
93
116
  // Airdrop SOL via LiteSVM
94
117
  await client.airdrop(koraAddress, lamports(solDropAmount));
95
118
  await client.airdrop(testWallet.address, lamports(solDropAmount));
@@ -1,6 +1,7 @@
1
1
  import { getTransferInstruction, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
2
2
  import { appendTransactionMessageInstructions, createNoopSigner, createTransactionMessage, generateKeyPairSigner, partiallySignTransactionMessageWithSigners, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, } from '@solana/kit';
3
3
  import { KoraClient } from '../src/client.js';
4
+ import { KoraError } from '../src/error.js';
4
5
  import { getInstructionsFromBase64Message } from '../src/utils/transaction.js';
5
6
  // Mock fetch globally
6
7
  const mockFetch = jest.fn();
@@ -69,7 +70,7 @@ describe('KoraClient Unit Tests', () => {
69
70
  it('should handle RPC error responses', async () => {
70
71
  const mockError = { code: -32601, message: 'Method not found' };
71
72
  mockErrorResponse(mockError);
72
- await expect(client.getConfig()).rejects.toThrow('RPC Error -32601: Method not found');
73
+ await expect(client.getConfig()).rejects.toThrow('Kora Error -32601: Method not found');
73
74
  });
74
75
  it('should handle network errors', async () => {
75
76
  mockFetch.mockRejectedValueOnce(new Error('Network error'));
@@ -238,6 +239,18 @@ describe('KoraClient Unit Tests', () => {
238
239
  };
239
240
  await testSuccessfulRpcMethod('signAndSendTransaction', () => client.signAndSendTransaction(request), mockResponse, request);
240
241
  });
242
+ it('should pass the respond_after milestone through to the RPC request', async () => {
243
+ const request = {
244
+ transaction: 'base64_encoded_transaction',
245
+ respond_after: 'signed',
246
+ };
247
+ const mockResponse = {
248
+ signature: 'transaction_signature',
249
+ signed_transaction: 'base64_signed_transaction',
250
+ signer_pubkey: 'test_signer_pubkey',
251
+ };
252
+ await testSuccessfulRpcMethod('signAndSendTransaction', () => client.signAndSendTransaction(request), mockResponse, request);
253
+ });
241
254
  });
242
255
  describe('signBundle', () => {
243
256
  it('should sign bundle of transactions', async () => {
@@ -256,7 +269,7 @@ describe('KoraClient Unit Tests', () => {
256
269
  };
257
270
  const mockError = { code: -32000, message: 'Bundle validation failed' };
258
271
  mockErrorResponse(mockError);
259
- await expect(client.signBundle(request)).rejects.toThrow('RPC Error -32000: Bundle validation failed');
272
+ await expect(client.signBundle(request)).rejects.toThrow('Kora Error -32000: Bundle validation failed');
260
273
  });
261
274
  });
262
275
  describe('signAndSendBundle', () => {
@@ -277,7 +290,7 @@ describe('KoraClient Unit Tests', () => {
277
290
  };
278
291
  const mockError = { code: -32000, message: 'Jito submission failed' };
279
292
  mockErrorResponse(mockError);
280
- await expect(client.signAndSendBundle(request)).rejects.toThrow('RPC Error -32000: Jito submission failed');
293
+ await expect(client.signAndSendBundle(request)).rejects.toThrow('Kora Error -32000: Jito submission failed');
281
294
  });
282
295
  });
283
296
  describe('getPaymentInstruction', () => {
@@ -465,7 +478,7 @@ describe('KoraClient Unit Tests', () => {
465
478
  jsonrpc: '2.0',
466
479
  }),
467
480
  });
468
- await expect(client.getPaymentInstruction(validRequest)).rejects.toThrow('RPC Error -32602: Invalid transaction');
481
+ await expect(client.getPaymentInstruction(validRequest)).rejects.toThrow('Kora Error -32602: Invalid transaction');
469
482
  });
470
483
  it('should handle network errors', async () => {
471
484
  mockFetch.mockRejectedValueOnce(new Error('Network error'));
@@ -564,11 +577,42 @@ describe('KoraClient Unit Tests', () => {
564
577
  it('should handle responses with an error object', async () => {
565
578
  const mockError = { code: -32602, message: 'Invalid params' };
566
579
  mockErrorResponse(mockError);
567
- await expect(client.getConfig()).rejects.toThrow('RPC Error -32602: Invalid params');
580
+ await expect(client.getConfig()).rejects.toThrow('Kora Error -32602: Invalid params');
568
581
  });
569
- it('should handle empty error object', async () => {
582
+ it('should handle empty error object gracefully', async () => {
570
583
  mockErrorResponse({});
571
- await expect(client.getConfig()).rejects.toThrow('RPC Error undefined: undefined');
584
+ try {
585
+ await client.getConfig();
586
+ throw new Error('Should have thrown KoraError');
587
+ }
588
+ catch (e) {
589
+ expect(e).toBeInstanceOf(KoraError);
590
+ expect(e.code).toBe(-32603);
591
+ expect(e.message).toBe('Kora Error -32603: Unknown error');
592
+ expect(e.data).toBeUndefined();
593
+ }
594
+ });
595
+ it('should populate KoraError properties (code and data)', async () => {
596
+ const mockError = {
597
+ code: -32050,
598
+ data: {
599
+ error_type: 'AccountNotFound',
600
+ },
601
+ message: 'Account not found message',
602
+ };
603
+ mockErrorResponse(mockError);
604
+ try {
605
+ await client.getConfig();
606
+ throw new Error('Should have thrown KoraError');
607
+ }
608
+ catch (e) {
609
+ expect(e).toBeInstanceOf(KoraError);
610
+ expect(e.code).toBe(-32050);
611
+ expect(e.data).toEqual({
612
+ error_type: 'AccountNotFound',
613
+ });
614
+ expect(e.message).toBe('Kora Error -32050: Account not found message');
615
+ }
572
616
  });
573
617
  });
574
618
  describe('reCAPTCHA Authentication', () => {
package/package.json CHANGED
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "name": "@solana/kora",
3
- "version": "0.3.0-beta.0",
3
+ "version": "0.3.0-beta.2",
4
4
  "description": "TypeScript SDK for Kora RPC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",
7
7
  "types": "dist/src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/src/index.d.ts",
11
+ "default": "./dist/src/index.js"
12
+ },
13
+ "./kit": {
14
+ "types": "./dist/src/kit/index.d.ts",
15
+ "default": "./dist/src/kit/index.js"
16
+ }
17
+ },
8
18
  "files": [
9
19
  "dist"
10
20
  ],
@@ -23,35 +33,35 @@
23
33
  },
24
34
  "peerDependencies": {
25
35
  "@solana-program/compute-budget": "^0.15.0",
26
- "@solana-program/token": "^0.12.0",
27
- "@solana/kit": "^6.3.0",
28
- "@solana/kit-plugin-instruction-plan": "^0.7.0",
29
- "@solana/kit-plugin-payer": "^0.6.0",
30
- "@solana/kit-plugin-rpc": "^0.7.0"
36
+ "@solana-program/token": "^0.13.0",
37
+ "@solana/kit": "^6.8.0",
38
+ "@solana/kit-plugin-instruction-plan": "^0.10.0",
39
+ "@solana/kit-plugin-rpc": "^0.10.0",
40
+ "@solana/kit-plugin-signer": "^0.10.0"
31
41
  },
32
42
  "devDependencies": {
33
- "@eslint/js": "^9.39.3",
43
+ "@eslint/js": "^10.0.1",
34
44
  "@solana-program/system": "^0.12.0",
35
45
  "@solana/eslint-config-solana": "6.0.0",
36
- "@solana/kit-client-litesvm": "^0.7.0",
46
+ "@solana/kit-plugin-litesvm": "^0.10.0",
37
47
  "@solana/prettier-config-solana": "^0.0.6",
38
- "@types/jest": "^29.5.12",
39
- "@types/node": "^20.17.27",
40
- "dotenv": "^16.4.5",
41
- "eslint": "^9.39.3",
42
- "eslint-plugin-jest": "^29.15.0",
43
- "eslint-plugin-simple-import-sort": "^12.1.1",
48
+ "@types/jest": "^30.0.0",
49
+ "@types/node": "^25.9.1",
50
+ "dotenv": "^17.4.2",
51
+ "eslint": "^10.4.0",
52
+ "eslint-plugin-jest": "^29.15.2",
53
+ "eslint-plugin-simple-import-sort": "^13.0.0",
44
54
  "eslint-plugin-sort-keys-fix": "^1.1.2",
45
55
  "eslint-plugin-typescript-sort-keys": "^3.3.0",
46
- "globals": "^16.5.0",
47
- "jest": "^29.7.0",
48
- "prettier": "^3.2.5",
49
- "ts-jest": "^29.1.2",
56
+ "globals": "^17.6.0",
57
+ "jest": "^30.4.2",
58
+ "prettier": "^3.8.3",
59
+ "ts-jest": "^29.4.9",
50
60
  "ts-node": "^10.9.2",
51
- "typedoc": "^0.28.9",
52
- "typedoc-plugin-markdown": "^4.8.0",
53
- "typescript": "^5.9.3",
54
- "typescript-eslint": "^8.56.1"
61
+ "typedoc": "^0.28.19",
62
+ "typedoc-plugin-markdown": "^4.12.0",
63
+ "typescript": "^6.0.3",
64
+ "typescript-eslint": "^8.61.0"
55
65
  },
56
66
  "scripts": {
57
67
  "build": "tsc",
@@ -62,10 +72,11 @@
62
72
  "test:integration": "pnpm test integration.test.ts",
63
73
  "test:integration:auth": "ENABLE_AUTH=true pnpm test integration.test.ts",
64
74
  "test:integration:free": "FREE_PRICING=true pnpm test integration.test.ts",
75
+ "test:integration:openfort": "KORA_SIGNER_TYPE=openfort pnpm test integration.test.ts",
65
76
  "test:integration:privy": "KORA_SIGNER_TYPE=privy pnpm test integration.test.ts",
66
77
  "test:integration:turnkey": "KORA_SIGNER_TYPE=turnkey pnpm test integration.test.ts",
67
- "test:unit": "pnpm test unit.test.ts",
68
- "test:ci:unit": "jest test/unit.test.ts",
78
+ "test:unit": "pnpm test unit.test.ts kit-client.test.ts plugin.test.ts",
79
+ "test:ci:unit": "jest test/unit.test.ts test/kit-client.test.ts test/plugin.test.ts",
69
80
  "lint": "eslint src/",
70
81
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
71
82
  "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",