@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.
- package/dist/src/client.d.ts +11 -0
- package/dist/src/client.js +14 -2
- package/dist/src/error.d.ts +41 -0
- package/dist/src/error.js +54 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/kit/executor.d.ts +2 -2
- package/dist/src/kit/executor.js +3 -3
- package/dist/src/kit/index.d.ts +81 -24
- package/dist/src/kit/index.js +76 -54
- package/dist/src/kit/planner.d.ts +2 -2
- package/dist/src/plugin.d.ts +3 -3
- package/dist/src/plugin.js +6 -4
- package/dist/src/types/index.d.ts +33 -7
- package/dist/test/integration.test.js +57 -9
- package/dist/test/kit-client.test.js +48 -3
- package/dist/test/plugin.test.js +43 -8
- package/dist/test/setup.js +30 -7
- package/dist/test/unit.test.js +51 -7
- package/package.json +35 -24
package/dist/src/client.d.ts
CHANGED
|
@@ -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>;
|
package/dist/src/client.js
CHANGED
|
@@ -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
|
|
83
|
-
throw new
|
|
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
|
+
}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -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 {
|
|
4
|
-
export declare function createKoraTransactionPlanExecutor(koraClient: KoraClient, config:
|
|
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>;
|
package/dist/src/kit/executor.js
CHANGED
|
@@ -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,
|
|
46
|
-
: removePaymentInstruction(currentIxs, sourceTokenAccount, destinationTokenAccount,
|
|
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),
|
package/dist/src/kit/index.d.ts
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
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
|
-
*
|
|
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
|
|
8
|
-
*
|
|
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
|
-
* ```
|
|
14
|
-
* import {
|
|
15
|
-
* import {
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
|
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>>;
|
package/dist/src/kit/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { address,
|
|
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 {
|
|
4
|
-
import {
|
|
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
|
-
*
|
|
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
|
|
15
|
-
*
|
|
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
|
-
* ```
|
|
21
|
-
* import {
|
|
22
|
-
* import {
|
|
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
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
|
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
|
|
40
|
-
|
|
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 {
|
|
3
|
-
export declare function buildComputeBudgetInstructions(config:
|
|
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;
|
package/dist/src/plugin.d.ts
CHANGED
|
@@ -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 `
|
|
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 {
|
|
18
|
+
* import { createClient } from '@solana/kit';
|
|
19
19
|
* import { koraPlugin } from '@solana/kora';
|
|
20
20
|
*
|
|
21
|
-
* const client =
|
|
21
|
+
* const client = createClient()
|
|
22
22
|
* .use(koraPlugin({ endpoint: 'https://kora.example.com' }));
|
|
23
23
|
*
|
|
24
24
|
* // All responses have Kit-typed fields
|
package/dist/src/plugin.js
CHANGED
|
@@ -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 `
|
|
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 {
|
|
19
|
+
* import { createClient } from '@solana/kit';
|
|
20
20
|
* import { koraPlugin } from '@solana/kora';
|
|
21
21
|
*
|
|
22
|
-
* const client =
|
|
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
|
|
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
|
-
/**
|
|
626
|
-
|
|
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 {
|
|
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
|
-
|
|
338
|
+
async function buildFreeClient() {
|
|
338
339
|
const koraRpcUrl = process.env.KORA_RPC_URL || 'http://127.0.0.1:8080';
|
|
339
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
+
});
|
package/dist/test/plugin.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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('
|
|
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('
|
|
421
|
+
describe('createClient Integration', () => {
|
|
387
422
|
it('should initialize kora property on Kit client', () => {
|
|
388
|
-
const client =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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');
|
package/dist/test/setup.js
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
import { assertIsAddress, createKeyPairSignerFromBytes, getBase58Encoder, lamports, } from '@solana/kit';
|
|
2
|
-
import {
|
|
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
|
-
//
|
|
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 ||
|
|
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(
|
|
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));
|
package/dist/test/unit.test.js
CHANGED
|
@@ -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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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.
|
|
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.
|
|
27
|
-
"@solana/kit": "^6.
|
|
28
|
-
"@solana/kit-plugin-instruction-plan": "^0.
|
|
29
|
-
"@solana/kit-plugin-
|
|
30
|
-
"@solana/kit-plugin-
|
|
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": "^
|
|
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-
|
|
46
|
+
"@solana/kit-plugin-litesvm": "^0.10.0",
|
|
37
47
|
"@solana/prettier-config-solana": "^0.0.6",
|
|
38
|
-
"@types/jest": "^
|
|
39
|
-
"@types/node": "^
|
|
40
|
-
"dotenv": "^
|
|
41
|
-
"eslint": "^
|
|
42
|
-
"eslint-plugin-jest": "^29.15.
|
|
43
|
-
"eslint-plugin-simple-import-sort": "^
|
|
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": "^
|
|
47
|
-
"jest": "^
|
|
48
|
-
"prettier": "^3.
|
|
49
|
-
"ts-jest": "^29.
|
|
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.
|
|
52
|
-
"typedoc-plugin-markdown": "^4.
|
|
53
|
-
"typescript": "^
|
|
54
|
-
"typescript-eslint": "^8.
|
|
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\"",
|