@solana/kora 0.1.0 → 0.1.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/README.md CHANGED
@@ -1,8 +1,35 @@
1
1
  # Kora TypeScript SDK
2
2
 
3
- A TypeScript SDK for interacting with the Kora RPC server. This SDK provides a type-safe interface to all Kora RPC methods.
3
+ A TypeScript SDK for interacting with the Kora RPC server. This SDK provides a type-safe interface to all Kora RPC methods (requires a Kora RPC server to be running).
4
4
 
5
- ## Development
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pnpm install @solana/kora
10
+ ```
11
+
12
+ ## Quick Start
13
+
14
+ ```typescript
15
+ import { KoraClient } from '@solana/kora';
16
+
17
+ // Initialize the client with your RPC endpoint
18
+ const client = new KoraClient({ rpcUrl: 'http://localhost:8080' });
19
+
20
+ // Example: Get Kora to sign a transaction
21
+ const result = await client.signTransaction({
22
+ transaction: 'myBase64EncodedTransaction'
23
+ });
24
+
25
+ // Access the signed transaction (base64 encoded)
26
+ console.log('Signed transaction:', result.signed_transaction);
27
+ ```
28
+
29
+ **[→ API Reference](https://launch.solana.com/docs/kora/json-rpc-api)**
30
+ **[→ Quick Start](https://launch.solana.com/docs/kora/getting-started/quick-start)**
31
+
32
+ ## Local Development
6
33
 
7
34
  ### Building from Source
8
35
 
@@ -33,28 +60,3 @@ pnpm test:ci:integration
33
60
 
34
61
  This will start a local test validator and run all tests.
35
62
 
36
-
37
- ## Quick Start
38
-
39
- ```typescript
40
- import { KoraClient } from '@solana/kora';
41
-
42
- // Initialize the client with your RPC endpoint
43
- const client = new KoraClient({ rpcUrl: 'http://localhost:8080' });
44
-
45
- // Example: Transfer tokens
46
- const result = await client.transferTransaction({
47
- amount: 1000000, // 1 USDC (6 decimals)
48
- token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
49
- source: "sourceAddress",
50
- destination: "destinationAddress"
51
- });
52
-
53
- // Access the base64 encoded transaction, base64 encoded message, and parsed instructions directly
54
- console.log('Transaction:', result.transaction);
55
- console.log('Message:', result.message);
56
- console.log('Instructions:', result.instructions);
57
- ```
58
-
59
- **[→ API Reference](https://launch.solana.com/docs/kora/json-rpc-api)**
60
- **[→ Quick Start](https://launch.solana.com/docs/kora/getting-started/quick-start)**
@@ -1,4 +1,3 @@
1
- // TODO Make sure to change necessary deps from devdeps to deps
2
1
  import { assertIsAddress, createNoopSigner } from '@solana/kit';
3
2
  import crypto from 'crypto';
4
3
  import { getInstructionsFromBase64Message } from './utils/transaction.js';
@@ -1,2 +1,3 @@
1
1
  export * from './types/index.js';
2
2
  export { KoraClient } from './client.js';
3
+ export { koraPlugin, type KoraApi } from './plugin.js';
package/dist/src/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './types/index.js';
2
2
  export { KoraClient } from './client.js';
3
+ export { koraPlugin } from './plugin.js';
@@ -0,0 +1,73 @@
1
+ import type { KoraPluginConfig, KitPayerSignerResponse, KitBlockhashResponse, KitSupportedTokensResponse, KitEstimateFeeResponse, KitSignTransactionResponse, KitSignAndSendTransactionResponse, KitTransferTransactionResponse, KitPaymentInstructionResponse, KitConfigResponse, EstimateTransactionFeeRequest, SignTransactionRequest, SignAndSendTransactionRequest, TransferTransactionRequest, GetPaymentInstructionRequest } from './types/index.js';
2
+ /**
3
+ * Creates a Kora Kit plugin that adds Kora paymaster functionality to a Kit client.
4
+ *
5
+ * The plugin exposes all Kora RPC methods with Kit-typed responses (Address, Blockhash).
6
+ *
7
+ * **Note:** The plugin pattern with `createEmptyClient().use()` requires `@solana/kit` v5.4.0+.
8
+ * For older kit versions, use `KoraClient` directly instead.
9
+ *
10
+ * @param config - Plugin configuration
11
+ * @param config.endpoint - Kora RPC endpoint URL
12
+ * @param config.apiKey - Optional API key for authentication
13
+ * @param config.hmacSecret - Optional HMAC secret for signature-based authentication
14
+ * @returns A Kit plugin function that adds `.kora` to the client
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { createEmptyClient } from '@solana/kit';
19
+ * import { koraPlugin } from '@solana/kora';
20
+ *
21
+ * const client = createEmptyClient()
22
+ * .use(koraPlugin({ endpoint: 'https://kora.example.com' }));
23
+ *
24
+ * // All responses have Kit-typed fields
25
+ * const config = await client.kora.getConfig();
26
+ * // config.fee_payers is Address[] not string[]
27
+ *
28
+ * const { signer_pubkey } = await client.kora.signTransaction({ transaction: tx });
29
+ * // signer_pubkey is Address not string
30
+ * ```
31
+ */
32
+ export declare function koraPlugin(config: KoraPluginConfig): <T extends object>(c: T) => T & {
33
+ kora: {
34
+ /**
35
+ * Retrieves the current Kora server configuration with Kit-typed addresses.
36
+ */
37
+ getConfig(): Promise<KitConfigResponse>;
38
+ /**
39
+ * Retrieves the payer signer and payment destination with Kit-typed addresses.
40
+ */
41
+ getPayerSigner(): Promise<KitPayerSignerResponse>;
42
+ /**
43
+ * Gets the latest blockhash with Kit Blockhash type.
44
+ */
45
+ getBlockhash(): Promise<KitBlockhashResponse>;
46
+ /**
47
+ * Retrieves the list of tokens supported for fee payment with Kit-typed addresses.
48
+ */
49
+ getSupportedTokens(): Promise<KitSupportedTokensResponse>;
50
+ /**
51
+ * Estimates the transaction fee with Kit-typed addresses.
52
+ */
53
+ estimateTransactionFee(request: EstimateTransactionFeeRequest): Promise<KitEstimateFeeResponse>;
54
+ /**
55
+ * Signs a transaction with Kit-typed response.
56
+ */
57
+ signTransaction(request: SignTransactionRequest): Promise<KitSignTransactionResponse>;
58
+ /**
59
+ * Signs and sends a transaction with Kit-typed response.
60
+ */
61
+ signAndSendTransaction(request: SignAndSendTransactionRequest): Promise<KitSignAndSendTransactionResponse>;
62
+ /**
63
+ * Creates a token transfer transaction with Kit-typed response.
64
+ */
65
+ transferTransaction(request: TransferTransactionRequest): Promise<KitTransferTransactionResponse>;
66
+ /**
67
+ * Creates a payment instruction with Kit-typed response.
68
+ */
69
+ getPaymentInstruction(request: GetPaymentInstructionRequest): Promise<KitPaymentInstructionResponse>;
70
+ };
71
+ };
72
+ /** Type representing the Kora API exposed by the plugin */
73
+ export type KoraApi = ReturnType<ReturnType<typeof koraPlugin>>['kora'];
@@ -0,0 +1,148 @@
1
+ import { address, blockhash } from '@solana/kit';
2
+ import { KoraClient } from './client.js';
3
+ /**
4
+ * Creates a Kora Kit plugin that adds Kora paymaster functionality to a Kit client.
5
+ *
6
+ * The plugin exposes all Kora RPC methods with Kit-typed responses (Address, Blockhash).
7
+ *
8
+ * **Note:** The plugin pattern with `createEmptyClient().use()` requires `@solana/kit` v5.4.0+.
9
+ * For older kit versions, use `KoraClient` directly instead.
10
+ *
11
+ * @param config - Plugin configuration
12
+ * @param config.endpoint - Kora RPC endpoint URL
13
+ * @param config.apiKey - Optional API key for authentication
14
+ * @param config.hmacSecret - Optional HMAC secret for signature-based authentication
15
+ * @returns A Kit plugin function that adds `.kora` to the client
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { createEmptyClient } from '@solana/kit';
20
+ * import { koraPlugin } from '@solana/kora';
21
+ *
22
+ * const client = createEmptyClient()
23
+ * .use(koraPlugin({ endpoint: 'https://kora.example.com' }));
24
+ *
25
+ * // All responses have Kit-typed fields
26
+ * const config = await client.kora.getConfig();
27
+ * // config.fee_payers is Address[] not string[]
28
+ *
29
+ * const { signer_pubkey } = await client.kora.signTransaction({ transaction: tx });
30
+ * // signer_pubkey is Address not string
31
+ * ```
32
+ */
33
+ export function koraPlugin(config) {
34
+ const client = new KoraClient({
35
+ rpcUrl: config.endpoint,
36
+ apiKey: config.apiKey,
37
+ hmacSecret: config.hmacSecret,
38
+ });
39
+ return (c) => ({
40
+ ...c,
41
+ kora: {
42
+ /**
43
+ * Retrieves the current Kora server configuration with Kit-typed addresses.
44
+ */
45
+ async getConfig() {
46
+ const result = await client.getConfig();
47
+ return {
48
+ fee_payers: result.fee_payers.map(addr => address(addr)),
49
+ validation_config: {
50
+ ...result.validation_config,
51
+ allowed_programs: result.validation_config.allowed_programs.map(addr => address(addr)),
52
+ allowed_tokens: result.validation_config.allowed_tokens.map(addr => address(addr)),
53
+ allowed_spl_paid_tokens: result.validation_config.allowed_spl_paid_tokens.map(addr => address(addr)),
54
+ disallowed_accounts: result.validation_config.disallowed_accounts.map(addr => address(addr)),
55
+ },
56
+ enabled_methods: result.enabled_methods,
57
+ };
58
+ },
59
+ /**
60
+ * Retrieves the payer signer and payment destination with Kit-typed addresses.
61
+ */
62
+ async getPayerSigner() {
63
+ const result = await client.getPayerSigner();
64
+ return {
65
+ signer_address: address(result.signer_address),
66
+ payment_address: address(result.payment_address),
67
+ };
68
+ },
69
+ /**
70
+ * Gets the latest blockhash with Kit Blockhash type.
71
+ */
72
+ async getBlockhash() {
73
+ const result = await client.getBlockhash();
74
+ return {
75
+ blockhash: blockhash(result.blockhash),
76
+ };
77
+ },
78
+ /**
79
+ * Retrieves the list of tokens supported for fee payment with Kit-typed addresses.
80
+ */
81
+ async getSupportedTokens() {
82
+ const result = await client.getSupportedTokens();
83
+ return {
84
+ tokens: result.tokens.map(addr => address(addr)),
85
+ };
86
+ },
87
+ /**
88
+ * Estimates the transaction fee with Kit-typed addresses.
89
+ */
90
+ async estimateTransactionFee(request) {
91
+ const result = await client.estimateTransactionFee(request);
92
+ return {
93
+ fee_in_lamports: result.fee_in_lamports,
94
+ fee_in_token: result.fee_in_token,
95
+ signer_pubkey: address(result.signer_pubkey),
96
+ payment_address: address(result.payment_address),
97
+ };
98
+ },
99
+ /**
100
+ * Signs a transaction with Kit-typed response.
101
+ */
102
+ async signTransaction(request) {
103
+ const result = await client.signTransaction(request);
104
+ return {
105
+ signed_transaction: result.signed_transaction,
106
+ signer_pubkey: address(result.signer_pubkey),
107
+ };
108
+ },
109
+ /**
110
+ * Signs and sends a transaction with Kit-typed response.
111
+ */
112
+ async signAndSendTransaction(request) {
113
+ const result = await client.signAndSendTransaction(request);
114
+ return {
115
+ signed_transaction: result.signed_transaction,
116
+ signer_pubkey: address(result.signer_pubkey),
117
+ };
118
+ },
119
+ /**
120
+ * Creates a token transfer transaction with Kit-typed response.
121
+ */
122
+ async transferTransaction(request) {
123
+ const result = await client.transferTransaction(request);
124
+ return {
125
+ transaction: result.transaction,
126
+ message: result.message,
127
+ blockhash: blockhash(result.blockhash),
128
+ signer_pubkey: address(result.signer_pubkey),
129
+ instructions: result.instructions,
130
+ };
131
+ },
132
+ /**
133
+ * Creates a payment instruction with Kit-typed response.
134
+ */
135
+ async getPaymentInstruction(request) {
136
+ const result = await client.getPaymentInstruction(request);
137
+ return {
138
+ original_transaction: result.original_transaction,
139
+ payment_instruction: result.payment_instruction,
140
+ payment_amount: result.payment_amount,
141
+ payment_token: address(result.payment_token),
142
+ payment_address: address(result.payment_address),
143
+ signer_address: address(result.signer_address),
144
+ };
145
+ },
146
+ },
147
+ });
148
+ }
@@ -381,3 +381,118 @@ export interface KoraClientOptions {
381
381
  /** Optional HMAC secret for signature-based authentication */
382
382
  hmacSecret?: string;
383
383
  }
384
+ /**
385
+ * Plugin Types - Kit-typed responses for the Kora plugin
386
+ */
387
+ import type { Address, Blockhash, Instruction as KitInstruction, Base64EncodedWireTransaction } from '@solana/kit';
388
+ /** Configuration options for the Kora Kit plugin */
389
+ export interface KoraPluginConfig {
390
+ /** Kora RPC endpoint URL */
391
+ endpoint: string;
392
+ /** Optional API key for authentication */
393
+ apiKey?: string;
394
+ /** Optional HMAC secret for signature-based authentication */
395
+ hmacSecret?: string;
396
+ }
397
+ /** Plugin response for getPayerSigner with Kit Address types */
398
+ export interface KitPayerSignerResponse {
399
+ /** Public key of the payer signer */
400
+ signer_address: Address;
401
+ /** Public key of the payment destination */
402
+ payment_address: Address;
403
+ }
404
+ /** Plugin response for getBlockhash with Kit Blockhash type */
405
+ export interface KitBlockhashResponse {
406
+ /** Base58-encoded blockhash */
407
+ blockhash: Blockhash;
408
+ }
409
+ /** Plugin response for getSupportedTokens with Kit Address types */
410
+ export interface KitSupportedTokensResponse {
411
+ /** Array of supported token mint addresses */
412
+ tokens: Address[];
413
+ }
414
+ /** Plugin response for estimateTransactionFee with Kit Address types */
415
+ export interface KitEstimateFeeResponse {
416
+ /** Transaction fee in lamports */
417
+ fee_in_lamports: number;
418
+ /** Transaction fee in the requested token */
419
+ fee_in_token: number;
420
+ /** Public key of the signer used to estimate the fee */
421
+ signer_pubkey: Address;
422
+ /** Public key of the payment destination */
423
+ payment_address: Address;
424
+ }
425
+ /** Plugin response for signTransaction with Kit types */
426
+ export interface KitSignTransactionResponse {
427
+ /** Base64-encoded signed transaction */
428
+ signed_transaction: Base64EncodedWireTransaction;
429
+ /** Public key of the signer used to sign the transaction */
430
+ signer_pubkey: Address;
431
+ }
432
+ /** Plugin response for signAndSendTransaction with Kit types */
433
+ export interface KitSignAndSendTransactionResponse {
434
+ /** Base64-encoded signed transaction */
435
+ signed_transaction: Base64EncodedWireTransaction;
436
+ /** Public key of the signer used to send the transaction */
437
+ signer_pubkey: Address;
438
+ }
439
+ /** Plugin response for transferTransaction with Kit types */
440
+ export interface KitTransferTransactionResponse {
441
+ /** Base64-encoded signed transaction */
442
+ transaction: Base64EncodedWireTransaction;
443
+ /** Base64-encoded message */
444
+ message: string;
445
+ /** Recent blockhash used in the transaction */
446
+ blockhash: Blockhash;
447
+ /** Public key of the signer used to send the transaction */
448
+ signer_pubkey: Address;
449
+ /** Parsed instructions from the transaction message */
450
+ instructions: KitInstruction[];
451
+ }
452
+ /** Plugin response for getPaymentInstruction with Kit types */
453
+ export interface KitPaymentInstructionResponse {
454
+ /** Base64-encoded original transaction */
455
+ original_transaction: Base64EncodedWireTransaction;
456
+ /** Payment instruction */
457
+ payment_instruction: KitInstruction;
458
+ /** Payment amount in the requested token */
459
+ payment_amount: number;
460
+ /** Mint address of the token used for payment */
461
+ payment_token: Address;
462
+ /** Public key of the payment destination */
463
+ payment_address: Address;
464
+ /** Public key of the payer signer */
465
+ signer_address: Address;
466
+ }
467
+ /** Plugin response for getConfig with Kit Address types */
468
+ export interface KitConfigResponse {
469
+ /** Array of public keys of the fee payer accounts (signer pool) */
470
+ fee_payers: Address[];
471
+ /** Validation rules and constraints */
472
+ validation_config: KitValidationConfig;
473
+ /** Enabled methods */
474
+ enabled_methods: EnabledMethods;
475
+ }
476
+ /** Plugin validation config with Kit Address types */
477
+ export interface KitValidationConfig {
478
+ /** Maximum allowed transaction value in lamports */
479
+ max_allowed_lamports: number;
480
+ /** Maximum number of signatures allowed per transaction */
481
+ max_signatures: number;
482
+ /** Price oracle source for token conversions */
483
+ price_source: PriceSource;
484
+ /** List of allowed Solana program IDs */
485
+ allowed_programs: Address[];
486
+ /** List of allowed token mint addresses for fee payment */
487
+ allowed_tokens: Address[];
488
+ /** List of SPL tokens accepted for paid transactions */
489
+ allowed_spl_paid_tokens: Address[];
490
+ /** List of blocked account addresses */
491
+ disallowed_accounts: Address[];
492
+ /** Policy controlling fee payer permissions */
493
+ fee_payer_policy: FeePayerPolicy;
494
+ /** Pricing model configuration */
495
+ price: PriceConfig;
496
+ /** Token2022 configuration */
497
+ token2022: Token2022Config;
498
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,386 @@
1
+ import { createEmptyClient } from '@solana/kit';
2
+ import { koraPlugin } from '../src/plugin.js';
3
+ // Mock fetch globally
4
+ const mockFetch = jest.fn();
5
+ global.fetch = mockFetch;
6
+ describe('Kora Kit Plugin', () => {
7
+ const mockEndpoint = 'http://localhost:8080';
8
+ const mockConfig = {
9
+ endpoint: mockEndpoint,
10
+ };
11
+ // Helper to mock successful RPC response
12
+ const mockSuccessfulResponse = (result) => {
13
+ mockFetch.mockResolvedValueOnce({
14
+ json: jest.fn().mockResolvedValueOnce({
15
+ jsonrpc: '2.0',
16
+ id: 1,
17
+ result,
18
+ }),
19
+ });
20
+ };
21
+ // Helper to mock error response
22
+ const mockErrorResponse = (error) => {
23
+ mockFetch.mockResolvedValueOnce({
24
+ json: jest.fn().mockResolvedValueOnce({
25
+ jsonrpc: '2.0',
26
+ id: 1,
27
+ error,
28
+ }),
29
+ });
30
+ };
31
+ beforeEach(() => {
32
+ mockFetch.mockClear();
33
+ });
34
+ describe('Plugin Composition', () => {
35
+ it('should add kora property to client', () => {
36
+ const baseClient = { existing: 'property' };
37
+ const plugin = koraPlugin(mockConfig);
38
+ const enhanced = plugin(baseClient);
39
+ expect(enhanced.existing).toBe('property');
40
+ expect(enhanced.kora).toBeDefined();
41
+ expect(typeof enhanced.kora.getConfig).toBe('function');
42
+ expect(typeof enhanced.kora.getPayerSigner).toBe('function');
43
+ expect(typeof enhanced.kora.getBlockhash).toBe('function');
44
+ expect(typeof enhanced.kora.getSupportedTokens).toBe('function');
45
+ expect(typeof enhanced.kora.estimateTransactionFee).toBe('function');
46
+ expect(typeof enhanced.kora.signTransaction).toBe('function');
47
+ expect(typeof enhanced.kora.signAndSendTransaction).toBe('function');
48
+ expect(typeof enhanced.kora.transferTransaction).toBe('function');
49
+ expect(typeof enhanced.kora.getPaymentInstruction).toBe('function');
50
+ });
51
+ it('should work with empty client object', () => {
52
+ const plugin = koraPlugin(mockConfig);
53
+ const enhanced = plugin({});
54
+ expect(enhanced.kora).toBeDefined();
55
+ });
56
+ it('should support authentication options', () => {
57
+ const authConfig = {
58
+ endpoint: mockEndpoint,
59
+ apiKey: 'test-api-key',
60
+ hmacSecret: 'test-hmac-secret',
61
+ };
62
+ const plugin = koraPlugin(authConfig);
63
+ const enhanced = plugin({});
64
+ expect(enhanced.kora).toBeDefined();
65
+ });
66
+ });
67
+ describe('Type Casting', () => {
68
+ let kora;
69
+ beforeEach(() => {
70
+ const plugin = koraPlugin(mockConfig);
71
+ const client = plugin({});
72
+ kora = client.kora;
73
+ });
74
+ describe('getConfig', () => {
75
+ it('should return Kit-typed Address arrays', async () => {
76
+ const rawResponse = {
77
+ fee_payers: ['DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7'],
78
+ validation_config: {
79
+ max_allowed_lamports: 1000000,
80
+ max_signatures: 10,
81
+ price_source: 'Jupiter',
82
+ allowed_programs: ['11111111111111111111111111111111'],
83
+ allowed_tokens: ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
84
+ allowed_spl_paid_tokens: ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
85
+ disallowed_accounts: [],
86
+ fee_payer_policy: {
87
+ system: {
88
+ allow_transfer: true,
89
+ allow_assign: true,
90
+ allow_create_account: true,
91
+ allow_allocate: true,
92
+ nonce: {
93
+ allow_initialize: true,
94
+ allow_advance: true,
95
+ allow_authorize: true,
96
+ allow_withdraw: true,
97
+ },
98
+ },
99
+ spl_token: {
100
+ allow_transfer: true,
101
+ allow_burn: true,
102
+ allow_close_account: true,
103
+ allow_approve: true,
104
+ allow_revoke: true,
105
+ allow_set_authority: true,
106
+ allow_mint_to: true,
107
+ allow_freeze_account: true,
108
+ allow_thaw_account: true,
109
+ },
110
+ token_2022: {
111
+ allow_transfer: true,
112
+ allow_burn: true,
113
+ allow_close_account: true,
114
+ allow_approve: true,
115
+ allow_revoke: true,
116
+ allow_set_authority: true,
117
+ allow_mint_to: true,
118
+ allow_freeze_account: true,
119
+ allow_thaw_account: true,
120
+ },
121
+ },
122
+ price: { type: 'margin', margin: 0.1 },
123
+ token2022: {
124
+ blocked_mint_extensions: [],
125
+ blocked_account_extensions: [],
126
+ },
127
+ },
128
+ enabled_methods: {
129
+ liveness: true,
130
+ estimate_transaction_fee: true,
131
+ get_supported_tokens: true,
132
+ sign_transaction: true,
133
+ sign_and_send_transaction: true,
134
+ transfer_transaction: true,
135
+ get_blockhash: true,
136
+ get_config: true,
137
+ },
138
+ };
139
+ mockSuccessfulResponse(rawResponse);
140
+ const result = await kora.getConfig();
141
+ // Verify type casting - these should be Address types
142
+ expect(result.fee_payers).toHaveLength(1);
143
+ expect(result.fee_payers[0]).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
144
+ expect(result.validation_config.allowed_programs).toHaveLength(1);
145
+ expect(result.validation_config.allowed_programs[0]).toBe('11111111111111111111111111111111');
146
+ expect(result.validation_config.allowed_tokens).toHaveLength(1);
147
+ expect(result.validation_config.allowed_tokens[0]).toBe('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
148
+ });
149
+ });
150
+ describe('getPayerSigner', () => {
151
+ it('should return Kit-typed Address fields', async () => {
152
+ const rawResponse = {
153
+ signer_address: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
154
+ payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
155
+ };
156
+ mockSuccessfulResponse(rawResponse);
157
+ const result = await kora.getPayerSigner();
158
+ // Type assertion - these should be Address types
159
+ const signerAddr = result.signer_address;
160
+ const paymentAddr = result.payment_address;
161
+ expect(signerAddr).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
162
+ expect(paymentAddr).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
163
+ });
164
+ });
165
+ describe('getBlockhash', () => {
166
+ it('should return Kit-typed Blockhash field', async () => {
167
+ const rawResponse = {
168
+ blockhash: '4NxM2D4kQcipkzMWBWQME5YSVnj5kT8QKA7rvb3rKLvE',
169
+ };
170
+ mockSuccessfulResponse(rawResponse);
171
+ const result = await kora.getBlockhash();
172
+ // Type assertion - should be Blockhash type
173
+ const hash = result.blockhash;
174
+ expect(hash).toBe('4NxM2D4kQcipkzMWBWQME5YSVnj5kT8QKA7rvb3rKLvE');
175
+ });
176
+ });
177
+ describe('getSupportedTokens', () => {
178
+ it('should return Kit-typed Address array', async () => {
179
+ const rawResponse = {
180
+ tokens: [
181
+ 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
182
+ 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
183
+ ],
184
+ };
185
+ mockSuccessfulResponse(rawResponse);
186
+ const result = await kora.getSupportedTokens();
187
+ // Type assertion - these should be Address types
188
+ expect(result.tokens).toHaveLength(2);
189
+ const token0 = result.tokens[0];
190
+ const token1 = result.tokens[1];
191
+ expect(token0).toBe('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
192
+ expect(token1).toBe('Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB');
193
+ });
194
+ });
195
+ describe('estimateTransactionFee', () => {
196
+ it('should return Kit-typed Address fields', async () => {
197
+ const rawResponse = {
198
+ fee_in_lamports: 5000,
199
+ fee_in_token: 50,
200
+ signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
201
+ payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
202
+ };
203
+ mockSuccessfulResponse(rawResponse);
204
+ const result = await kora.estimateTransactionFee({
205
+ transaction: 'base64EncodedTransaction',
206
+ fee_token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
207
+ });
208
+ // Type assertions
209
+ const signerPubkey = result.signer_pubkey;
210
+ const paymentAddr = result.payment_address;
211
+ expect(result.fee_in_lamports).toBe(5000);
212
+ expect(result.fee_in_token).toBe(50);
213
+ expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
214
+ expect(paymentAddr).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
215
+ });
216
+ });
217
+ describe('signTransaction', () => {
218
+ it('should return Kit-typed response with Base64EncodedWireTransaction', async () => {
219
+ const rawResponse = {
220
+ signed_transaction: 'base64SignedTransaction',
221
+ signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
222
+ };
223
+ mockSuccessfulResponse(rawResponse);
224
+ const result = await kora.signTransaction({
225
+ transaction: 'base64EncodedTransaction',
226
+ });
227
+ // Type assertions - verify Kit types
228
+ const signedTx = result.signed_transaction;
229
+ const signerPubkey = result.signer_pubkey;
230
+ expect(signedTx).toBe('base64SignedTransaction');
231
+ expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
232
+ });
233
+ });
234
+ describe('signAndSendTransaction', () => {
235
+ it('should return Kit-typed response with Base64EncodedWireTransaction', async () => {
236
+ const rawResponse = {
237
+ signed_transaction: 'base64SignedTransaction',
238
+ signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
239
+ };
240
+ mockSuccessfulResponse(rawResponse);
241
+ const result = await kora.signAndSendTransaction({
242
+ transaction: 'base64EncodedTransaction',
243
+ });
244
+ // Type assertions - verify Kit types
245
+ const signedTx = result.signed_transaction;
246
+ const signerPubkey = result.signer_pubkey;
247
+ expect(signedTx).toBe('base64SignedTransaction');
248
+ expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
249
+ });
250
+ });
251
+ describe('transferTransaction', () => {
252
+ it('should return Kit-typed response with Base64EncodedWireTransaction and Blockhash', async () => {
253
+ const rawResponse = {
254
+ transaction: 'base64Transaction',
255
+ message: 'base64Message',
256
+ blockhash: '4NxM2D4kQcipkzMWBWQME5YSVnj5kT8QKA7rvb3rKLvE',
257
+ signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
258
+ instructions: [],
259
+ };
260
+ mockSuccessfulResponse(rawResponse);
261
+ const result = await kora.transferTransaction({
262
+ amount: 1000000,
263
+ token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
264
+ source: 'sourceWallet',
265
+ destination: 'destWallet',
266
+ });
267
+ // Type assertions - verify Kit types
268
+ const tx = result.transaction;
269
+ const hash = result.blockhash;
270
+ const signerPubkey = result.signer_pubkey;
271
+ expect(tx).toBe('base64Transaction');
272
+ expect(result.message).toBe('base64Message');
273
+ expect(hash).toBe('4NxM2D4kQcipkzMWBWQME5YSVnj5kT8QKA7rvb3rKLvE');
274
+ expect(signerPubkey).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
275
+ expect(result.instructions).toEqual([]);
276
+ });
277
+ });
278
+ describe('getPaymentInstruction', () => {
279
+ it('should return Kit-typed response with Base64EncodedWireTransaction and Address fields', async () => {
280
+ const mockFeeEstimate = {
281
+ fee_in_lamports: 5000,
282
+ fee_in_token: 50000,
283
+ signer_pubkey: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
284
+ payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
285
+ };
286
+ const testTx = 'Aoq7ymA5OGP+gmDXiY5m3cYXlY2Rz/a/gFjOgt9ZuoCS7UzuiGGaEnW2OOtvHvMQHkkD7Z4LRF5B63ftu+1oZwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgECB1urjQEjgFgzqYhJ8IXJeSg4cJP1j1g2CJstOQTDchOKUzqH3PxgGW3c4V3vZV05A5Y30/MggOBs0Kd00s1JEwg5TaEeaV4+KL2y7fXIAuf6cN0ZQitbhY+G9ExtBSChspOXPgNcy9pYpETe4bmB+fg4bfZx1tnicA/kIyyubczAmbcIKIuniNOOQYG2ggKCz8NjEsHVezrWMatndu1wk6J5miGP26J6Vwp31AljiAajAFuP0D9mWJwSeFuA7J5rPwbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpd/O36SW02zRtNtqk6GFeip2+yBQsVTeSbLL4rWJRkd4CBgQCBQQBCgxAQg8AAAAAAAYGBAIFAwEKDBAnAAAAAAAABg==';
287
+ mockSuccessfulResponse(mockFeeEstimate);
288
+ const result = await kora.getPaymentInstruction({
289
+ transaction: testTx,
290
+ fee_token: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
291
+ source_wallet: '11111111111111111111111111111111',
292
+ token_program_id: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
293
+ });
294
+ // Type assertions - verify Kit types
295
+ const originalTx = result.original_transaction;
296
+ const paymentToken = result.payment_token;
297
+ const paymentAddr = result.payment_address;
298
+ const signerAddr = result.signer_address;
299
+ expect(originalTx).toBe(testTx);
300
+ expect(paymentToken).toBe('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU');
301
+ expect(paymentAddr).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
302
+ expect(signerAddr).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
303
+ expect(result.payment_amount).toBe(50000);
304
+ });
305
+ });
306
+ });
307
+ describe('Error Handling', () => {
308
+ let kora;
309
+ beforeEach(() => {
310
+ const plugin = koraPlugin(mockConfig);
311
+ const client = plugin({});
312
+ kora = client.kora;
313
+ });
314
+ it('should propagate RPC errors', async () => {
315
+ mockErrorResponse({ code: -32601, message: 'Method not found' });
316
+ await expect(kora.getConfig()).rejects.toThrow('RPC Error -32601: Method not found');
317
+ });
318
+ it('should propagate network errors', async () => {
319
+ mockFetch.mockRejectedValueOnce(new Error('Network error'));
320
+ await expect(kora.getConfig()).rejects.toThrow('Network error');
321
+ });
322
+ });
323
+ describe('KoraApi Type Export', () => {
324
+ it('should export KoraApi type correctly', () => {
325
+ // This test verifies the KoraApi type is correctly exported
326
+ const plugin = koraPlugin(mockConfig);
327
+ const client = plugin({});
328
+ // Type check - assign to KoraApi type
329
+ const api = client.kora;
330
+ expect(api).toBeDefined();
331
+ });
332
+ });
333
+ describe('createEmptyClient Integration', () => {
334
+ it('should initialize kora property on Kit client', () => {
335
+ const client = createEmptyClient().use(koraPlugin(mockConfig));
336
+ expect(client).toHaveProperty('kora');
337
+ expect(client.kora).toBeDefined();
338
+ });
339
+ it('should expose all Kora RPC methods', () => {
340
+ const client = createEmptyClient().use(koraPlugin(mockConfig));
341
+ expect(typeof client.kora.getConfig).toBe('function');
342
+ expect(typeof client.kora.getPayerSigner).toBe('function');
343
+ expect(typeof client.kora.getBlockhash).toBe('function');
344
+ expect(typeof client.kora.getSupportedTokens).toBe('function');
345
+ expect(typeof client.kora.estimateTransactionFee).toBe('function');
346
+ expect(typeof client.kora.signTransaction).toBe('function');
347
+ expect(typeof client.kora.signAndSendTransaction).toBe('function');
348
+ expect(typeof client.kora.transferTransaction).toBe('function');
349
+ expect(typeof client.kora.getPaymentInstruction).toBe('function');
350
+ });
351
+ it('should work with authentication config', () => {
352
+ const authConfig = {
353
+ endpoint: mockEndpoint,
354
+ apiKey: 'test-api-key',
355
+ hmacSecret: 'test-hmac-secret',
356
+ };
357
+ const client = createEmptyClient().use(koraPlugin(authConfig));
358
+ expect(client.kora).toBeDefined();
359
+ expect(typeof client.kora.getConfig).toBe('function');
360
+ });
361
+ it('should compose with other plugins', () => {
362
+ // Simulate another plugin that adds a different property
363
+ const otherPlugin = (c) => ({
364
+ ...c,
365
+ other: { foo: () => 'bar' },
366
+ });
367
+ const client = createEmptyClient().use(koraPlugin(mockConfig)).use(otherPlugin);
368
+ // Both plugins should be available
369
+ expect(client.kora).toBeDefined();
370
+ expect(client.other).toBeDefined();
371
+ expect(typeof client.kora.getConfig).toBe('function');
372
+ expect(client.other.foo()).toBe('bar');
373
+ });
374
+ it('should call RPC methods correctly', async () => {
375
+ const mockResponse = {
376
+ signer_address: 'DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
377
+ payment_address: 'PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7',
378
+ };
379
+ mockSuccessfulResponse(mockResponse);
380
+ const client = createEmptyClient().use(koraPlugin(mockConfig));
381
+ const result = await client.kora.getPayerSigner();
382
+ expect(result.signer_address).toBe('DemoKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
383
+ expect(result.payment_address).toBe('PayKMZWkk483QoFPLRPQ2XVKB7bWnuXwSjvDE1JsWk7');
384
+ });
385
+ });
386
+ });
@@ -1,6 +1,6 @@
1
1
  import { getCreateAccountInstruction } from '@solana-program/system';
2
2
  import { findAssociatedTokenPda, getCreateAssociatedTokenIdempotentInstructionAsync, getInitializeMintInstruction, getMintSize, getMintToInstruction, TOKEN_PROGRAM_ADDRESS, } from '@solana-program/token';
3
- import { airdropFactory, createSolanaRpc, createSolanaRpcSubscriptions, lamports, sendAndConfirmTransactionFactory, pipe, createTransactionMessage, setTransactionMessageLifetimeUsingBlockhash, setTransactionMessageFeePayerSigner, appendTransactionMessageInstructions, signTransactionMessageWithSigners, getSignatureFromTransaction, assertIsAddress, createKeyPairSignerFromBytes, getBase58Encoder, } from '@solana/kit';
3
+ import { airdropFactory, createSolanaRpc, createSolanaRpcSubscriptions, lamports, sendAndConfirmTransactionFactory, pipe, createTransactionMessage, setTransactionMessageLifetimeUsingBlockhash, setTransactionMessageFeePayerSigner, appendTransactionMessageInstructions, signTransactionMessageWithSigners, getSignatureFromTransaction, assertIsAddress, createKeyPairSignerFromBytes, getBase58Encoder, assertIsSendableTransaction, assertIsTransactionWithBlockhashLifetime, } from '@solana/kit';
4
4
  import { updateOrAppendSetComputeUnitLimitInstruction, updateOrAppendSetComputeUnitPriceInstruction, MAX_COMPUTE_UNIT_LIMIT, } from '@solana-program/compute-budget';
5
5
  import { config } from 'dotenv';
6
6
  import path from 'path';
@@ -94,6 +94,8 @@ const createDefaultTransaction = async (client, feePayer, computeLimit = MAX_COM
94
94
  const signAndSendTransaction = async (client, transactionMessage, commitment) => {
95
95
  const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
96
96
  const signature = getSignatureFromTransaction(signedTransaction);
97
+ assertIsSendableTransaction(signedTransaction);
98
+ assertIsTransactionWithBlockhashLifetime(signedTransaction);
97
99
  await sendAndConfirmTransactionFactory(client)(signedTransaction, { commitment, skipPreflight: true });
98
100
  return signature;
99
101
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana/kora",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "TypeScript SDK for Kora RPC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",
@@ -16,12 +16,16 @@
16
16
  ],
17
17
  "author": "",
18
18
  "license": "MIT",
19
+ "peerDependencies": {
20
+ "@solana-program/token": "^0.9.0",
21
+ "@solana/kit": "^5.0.0"
22
+ },
19
23
  "devDependencies": {
20
- "@solana-program/compute-budget": "^0.8.0",
21
- "@solana-program/system": "^0.7.0",
22
- "@solana-program/token": "^0.5.1",
23
- "@solana/kit": "^2.3.0",
24
- "@solana/prettier-config-solana": "^0.0.5",
24
+ "@solana-program/compute-budget": "^0.11.0",
25
+ "@solana-program/system": "^0.10.0",
26
+ "@solana-program/token": "^0.9.0",
27
+ "@solana/kit": "^5.4.0",
28
+ "@solana/prettier-config-solana": "^0.0.6",
25
29
  "@types/jest": "^29.5.12",
26
30
  "@types/node": "^20.17.27",
27
31
  "@typescript-eslint/eslint-plugin": "^8.38.0",
@@ -34,7 +38,8 @@
34
38
  "ts-node": "^10.9.2",
35
39
  "typedoc": "^0.28.9",
36
40
  "typedoc-plugin-markdown": "^4.8.0",
37
- "typescript": "^5.3.3"
41
+ "typescript": "^5.3.3",
42
+ "ws": "^8.18.3"
38
43
  },
39
44
  "scripts": {
40
45
  "build": "tsc",