@solana/kora 0.2.1 → 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.
@@ -1,25 +1,37 @@
1
- import { getCreateAccountInstruction } from '@solana-program/system';
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, assertIsSendableTransaction, assertIsTransactionWithBlockhashLifetime, } from '@solana/kit';
4
- import { updateOrAppendSetComputeUnitLimitInstruction, updateOrAppendSetComputeUnitPriceInstruction, MAX_COMPUTE_UNIT_LIMIT, } from '@solana-program/compute-budget';
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';
4
+ import { tokenProgram, associatedTokenProgram } from '@solana-program/token';
5
5
  import { config } from 'dotenv';
6
6
  import path from 'path';
7
7
  import { KoraClient } from '../src/index.js';
8
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';
9
23
  const DEFAULTS = {
10
24
  DECIMALS: 6,
11
- TOKEN_DROP_AMOUNT: 100_000,
25
+ // Make sure this matches the USDC mint in kora.toml (9BgeTKqmFsPVnfYscfM6NvsgmZxei7XfdciShQ6D3bxJ)
26
+ DESTINATION_ADDRESS: 'AVmDft8deQEo78bRKcGN5ZMf3hyjeLBK4Rd4xGB46yQM',
27
+ // DO NOT USE THESE KEYPAIRS IN PRODUCTION, TESTING KEYPAIRS ONLY
28
+ KORA_ADDRESS: '7AqpcUvgJ7Kh1VmJZ44rWp2XDow33vswo9VK9VqpPU2d',
12
29
  KORA_RPC_URL: 'http://localhost:8080/',
13
- SOLANA_RPC_URL: 'http://127.0.0.1:8899',
14
- SOLANA_WS_URL: 'ws://127.0.0.1:8900',
15
- COMMITMENT: 'processed',
30
+ KORA_SIGNER_TYPE: 'memory',
16
31
  SOL_DROP_AMOUNT: 1_000_000_000,
17
- // DO NOT USE THESE KEYPAIRS IN PRODUCTION, TESTING KEYPAIRS ONLY
18
- KORA_ADDRESS: '7AqpcUvgJ7Kh1VmJZ44rWp2XDow33vswo9VK9VqpPU2d', // Make sure this matches the kora-rpc signer address on launch (root .env)
19
- SENDER_SECRET: 'tzgfgSWTE3KUA6qfRoFYLaSfJm59uUeZRDy4ybMrLn1JV2drA1mftiaEcVFvq1Lok6h6EX2C4Y9kSKLvQWyMpS5', // HhA5j2rRiPbMrpF2ZD36r69FyZf3zWmEHRNSZbbNdVjf
20
- TEST_USDC_MINT_SECRET: '59kKmXphL5UJANqpFFjtH17emEq3oRNmYsx6a3P3vSGJRmhMgVdzH77bkNEi9bArRViT45e8L2TsuPxKNFoc3Qfg', // Make sure this matches the USDC mint in kora.toml (9BgeTKqmFsPVnfYscfM6NvsgmZxei7XfdciShQ6D3bxJ)
21
- DESTINATION_ADDRESS: 'AVmDft8deQEo78bRKcGN5ZMf3hyjeLBK4Rd4xGB46yQM',
22
- KORA_SIGNER_TYPE: 'memory', // Default signer type
32
+ // 9BgeTKqmFsPVnfYscfM6NvsgmZxei7XfdciShQ6D3bxJ
33
+ TEST_USDC_MINT_SECRET: '59kKmXphL5UJANqpFFjtH17emEq3oRNmYsx6a3P3vSGJRmhMgVdzH77bkNEi9bArRViT45e8L2TsuPxKNFoc3Qfg',
34
+ TOKEN_DROP_AMOUNT: 100_000, // Default signer type
23
35
  };
24
36
  const createKeyPairSignerFromB58Secret = async (b58Secret) => {
25
37
  const base58Encoder = getBase58Encoder();
@@ -44,6 +56,12 @@ export function loadEnvironmentVariables() {
44
56
  throw new Error('PRIVY_PUBLIC_KEY must be set when using Privy signer');
45
57
  }
46
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;
47
65
  case 'memory':
48
66
  default:
49
67
  koraAddress = DEFAULTS.KORA_ADDRESS;
@@ -51,30 +69,24 @@ export function loadEnvironmentVariables() {
51
69
  }
52
70
  }
53
71
  const koraRpcUrl = process.env.KORA_RPC_URL || DEFAULTS.KORA_RPC_URL;
54
- const solanaRpcUrl = process.env.SOLANA_RPC_URL || DEFAULTS.SOLANA_RPC_URL;
55
- const solanaWsUrl = process.env.SOLANA_WS_URL || DEFAULTS.SOLANA_WS_URL;
56
- const commitment = (process.env.COMMITMENT || DEFAULTS.COMMITMENT);
57
72
  const tokenDecimals = Number(process.env.TOKEN_DECIMALS || DEFAULTS.DECIMALS);
58
73
  const tokenDropAmount = Number(process.env.TOKEN_DROP_AMOUNT || DEFAULTS.TOKEN_DROP_AMOUNT);
59
74
  const solDropAmount = BigInt(process.env.SOL_DROP_AMOUNT || DEFAULTS.SOL_DROP_AMOUNT);
60
- const testWalletSecret = process.env.SENDER_SECRET || DEFAULTS.SENDER_SECRET;
75
+ const testWalletSecret = process.env.SENDER_SECRET || FLAVOR_WALLET_SECRETS[SUITE_FLAVOR];
61
76
  const testUsdcMintSecret = process.env.TEST_USDC_MINT_SECRET || DEFAULTS.TEST_USDC_MINT_SECRET;
62
77
  const destinationAddress = process.env.DESTINATION_ADDRESS || DEFAULTS.DESTINATION_ADDRESS;
63
78
  assertIsAddress(destinationAddress);
64
79
  assertIsAddress(koraAddress);
65
80
  return {
66
- koraRpcUrl,
81
+ destinationAddress,
67
82
  koraAddress,
83
+ koraRpcUrl,
68
84
  koraSignerType,
69
- commitment,
70
- tokenDecimals,
71
- tokenDropAmount,
72
85
  solDropAmount,
73
- solanaRpcUrl,
74
- solanaWsUrl,
75
- testWalletSecret,
76
86
  testUsdcMintSecret,
77
- destinationAddress,
87
+ testWalletSecret,
88
+ tokenDecimals,
89
+ tokenDropAmount,
78
90
  };
79
91
  }
80
92
  async function createKeyPairSigners() {
@@ -82,151 +94,62 @@ async function createKeyPairSigners() {
82
94
  const testWallet = await createKeyPairSignerFromB58Secret(testWalletSecret);
83
95
  const usdcMint = await createKeyPairSignerFromB58Secret(testUsdcMintSecret);
84
96
  return {
97
+ destinationAddress,
85
98
  testWallet,
86
99
  usdcMint,
87
- destinationAddress,
88
100
  };
89
101
  }
90
- const createDefaultTransaction = async (client, feePayer, computeLimit = MAX_COMPUTE_UNIT_LIMIT, feeMicroLamports = 1n) => {
91
- const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();
92
- return pipe(createTransactionMessage({ version: 0 }), tx => setTransactionMessageFeePayerSigner(feePayer, tx), tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), tx => updateOrAppendSetComputeUnitPriceInstruction(feeMicroLamports, tx), tx => updateOrAppendSetComputeUnitLimitInstruction(computeLimit, tx));
93
- };
94
- const signAndSendTransaction = async (client, transactionMessage, commitment) => {
95
- const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
96
- const signature = getSignatureFromTransaction(signedTransaction);
97
- assertIsSendableTransaction(signedTransaction);
98
- assertIsTransactionWithBlockhashLifetime(signedTransaction);
99
- await sendAndConfirmTransactionFactory(client)(signedTransaction, { commitment, skipPreflight: true });
100
- return signature;
101
- };
102
- function safeStringify(obj) {
103
- return JSON.stringify(obj, (key, value) => {
104
- if (typeof value === 'bigint') {
105
- return value.toString();
106
- }
107
- return value;
108
- }, 2);
109
- }
110
- async function sendAndConfirmInstructions(client, payer, instructions, description, commitment = loadEnvironmentVariables().commitment) {
111
- try {
112
- const signature = await pipe(await createDefaultTransaction(client, payer, 200_000), tx => appendTransactionMessageInstructions(instructions, tx), tx => signAndSendTransaction(client, tx, commitment));
113
- return signature;
114
- }
115
- catch (error) {
116
- console.error(safeStringify(error));
117
- throw new Error(`Failed to ${description.toLowerCase()}: ${error instanceof Error ? error.message : 'Unknown error'}`);
118
- }
119
- }
120
- async function initializeToken({ client, mintAuthority, payer, owner, mint, dropAmount, decimals, otherAtaWallets, }) {
121
- // Get Owner ATA
122
- const [ata] = await findAssociatedTokenPda({
123
- mint: mint.address,
124
- owner: owner.address,
125
- tokenProgram: TOKEN_PROGRAM_ADDRESS,
126
- });
127
- // Get Mint size & rent
128
- const mintSpace = BigInt(getMintSize());
129
- const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();
130
- // Create instructions for new token mint
131
- const baseInstructions = [
132
- // Create the Mint Account
133
- getCreateAccountInstruction({
134
- payer,
135
- newAccount: mint,
136
- lamports: mintRent,
137
- space: mintSpace,
138
- programAddress: TOKEN_PROGRAM_ADDRESS,
139
- }),
140
- // Initialize the Mint
141
- getInitializeMintInstruction({
142
- mint: mint.address,
143
- decimals,
144
- mintAuthority: mintAuthority.address,
145
- }),
146
- // Create Associated Token Account
147
- await getCreateAssociatedTokenIdempotentInstructionAsync({
148
- mint: mint.address,
149
- payer,
150
- owner: owner.address,
151
- }),
152
- // Mint To the Destination Associated Token Account
153
- getMintToInstruction({
154
- mint: mint.address,
155
- token: ata,
156
- amount: BigInt(dropAmount * 10 ** decimals),
157
- mintAuthority,
158
- }),
159
- ];
160
- // Generate Create ATA instructions for other token accounts we wish to add
161
- const otherAtaInstructions = otherAtaWallets
162
- ? await Promise.all(otherAtaWallets.map(async (wallet) => await getCreateAssociatedTokenIdempotentInstructionAsync({
163
- mint: mint.address,
164
- payer,
165
- owner: wallet,
166
- })))
167
- : [];
168
- const alreadyExists = await mintExists(client, mint.address);
169
- let instructions = alreadyExists ? [...otherAtaInstructions] : [...baseInstructions, ...otherAtaInstructions];
170
- await sendAndConfirmInstructions(client, payer, instructions, 'Initialize token and ATAs', 'finalized');
171
- }
172
102
  async function setupTestSuite() {
173
- const { koraAddress, koraRpcUrl, commitment, tokenDecimals, tokenDropAmount, solDropAmount, solanaRpcUrl, solanaWsUrl, } = await loadEnvironmentVariables();
174
- // Load auth config from environment if not provided
103
+ const { koraAddress, koraRpcUrl, tokenDecimals, tokenDropAmount, solDropAmount } = loadEnvironmentVariables();
175
104
  const authConfig = process.env.ENABLE_AUTH === 'true'
176
105
  ? {
177
106
  apiKey: process.env.KORA_API_KEY || 'test-api-key-123',
178
107
  hmacSecret: process.env.KORA_HMAC_SECRET || 'test-hmac-secret-456',
179
108
  }
180
109
  : undefined;
181
- // Create Solana client
182
- const rpc = createSolanaRpc(solanaRpcUrl);
183
- const rpcSubscriptions = createSolanaRpcSubscriptions(solanaWsUrl);
184
- const airdrop = airdropFactory({ rpc, rpcSubscriptions });
185
- const client = { rpc, rpcSubscriptions };
186
- // Get or create keypairs
187
110
  const { testWallet, usdcMint, destinationAddress } = await createKeyPairSigners();
188
- const mintAuthority = testWallet; // test wallet can be used as mint authority for the test
189
- // Airdrop SOL to test sender and kora wallets
190
- await Promise.all([
191
- airdrop({
192
- commitment: 'finalized',
193
- lamports: lamports(solDropAmount),
194
- recipientAddress: koraAddress,
195
- }),
196
- airdrop({
197
- commitment: 'finalized',
198
- lamports: lamports(solDropAmount),
199
- recipientAddress: testWallet.address,
200
- }),
201
- ]);
202
- // Initialize token and ATAs
203
- await initializeToken({
204
- client,
205
- mintAuthority,
206
- payer: mintAuthority,
207
- owner: testWallet,
208
- mint: usdcMint,
209
- dropAmount: tokenDropAmount,
111
+ const client = await createClient()
112
+ .use(payer(testWallet))
113
+ .use(litesvm())
114
+ .use(tokenProgram())
115
+ .use(associatedTokenProgram());
116
+ // Airdrop SOL via LiteSVM
117
+ await client.airdrop(koraAddress, lamports(solDropAmount));
118
+ await client.airdrop(testWallet.address, lamports(solDropAmount));
119
+ // Create mint
120
+ await client.token.instructions
121
+ .createMint({
122
+ newMint: usdcMint,
210
123
  decimals: tokenDecimals,
211
- otherAtaWallets: [testWallet.address, koraAddress, destinationAddress],
212
- });
124
+ mintAuthority: testWallet.address,
125
+ })
126
+ .sendTransaction();
127
+ // Mint tokens to testWallet's ATA (auto-creates ATA)
128
+ await client.token.instructions
129
+ .mintToATA({
130
+ mint: usdcMint.address,
131
+ owner: testWallet.address,
132
+ mintAuthority: testWallet,
133
+ amount: BigInt(tokenDropAmount * 10 ** tokenDecimals),
134
+ decimals: tokenDecimals,
135
+ })
136
+ .sendTransaction();
137
+ // Create ATAs for kora and destination wallets
138
+ for (const owner of [koraAddress, destinationAddress]) {
139
+ await client.associatedToken.instructions
140
+ .createAssociatedTokenIdempotent({
141
+ owner,
142
+ mint: usdcMint.address,
143
+ })
144
+ .sendTransaction();
145
+ }
213
146
  return {
147
+ destinationAddress,
148
+ koraAddress,
214
149
  koraClient: new KoraClient({ rpcUrl: koraRpcUrl, ...authConfig }),
215
150
  koraRpcUrl,
216
151
  testWallet,
217
152
  usdcMint: usdcMint.address,
218
- destinationAddress,
219
- koraAddress,
220
- authConfig,
221
153
  };
222
154
  }
223
- const mintExists = async (client, mint) => {
224
- try {
225
- const mintAccount = await client.rpc.getAccountInfo(mint).send();
226
- return mintAccount.value !== null;
227
- }
228
- catch (error) {
229
- return false;
230
- }
231
- };
232
155
  export default setupTestSuite;