@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.
- package/dist/src/client.d.ts +212 -7
- package/dist/src/client.js +234 -17
- package/dist/src/error.d.ts +41 -0
- package/dist/src/error.js +54 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +2 -2
- package/dist/src/kit/executor.d.ts +2 -2
- package/dist/src/kit/executor.js +20 -7
- package/dist/src/kit/index.d.ts +85 -25
- package/dist/src/kit/index.js +79 -49
- package/dist/src/kit/payment.js +3 -2
- package/dist/src/kit/planner.d.ts +2 -2
- package/dist/src/plugin.d.ts +85 -0
- package/dist/src/plugin.js +179 -0
- package/dist/src/types/index.d.ts +200 -80
- package/dist/test/auth-setup.js +4 -4
- package/dist/test/integration.test.js +223 -298
- package/dist/test/kit-client.test.js +66 -3
- package/dist/test/plugin.test.js +169 -79
- package/dist/test/setup.d.ts +8 -15
- package/dist/test/setup.js +76 -153
- package/dist/test/unit.test.js +365 -161
- package/package.json +37 -34
- package/dist/src/kit/plugin.d.ts +0 -31
- package/dist/src/kit/plugin.js +0 -107
package/dist/test/setup.js
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
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';
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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;
|