naracli 0.1.0
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 +155 -0
- package/bin/nara-cli.ts +32 -0
- package/dist/nara-cli.mjs +2631 -0
- package/dist/quest/nara_quest.json +534 -0
- package/dist/zk/answer_proof.wasm +0 -0
- package/dist/zk/answer_proof_final.zkey +0 -0
- package/index.ts +76 -0
- package/package.json +54 -0
- package/src/cli/commands/config.ts +125 -0
- package/src/cli/commands/migrate.ts +270 -0
- package/src/cli/commands/pool.ts +364 -0
- package/src/cli/commands/quest.ts +312 -0
- package/src/cli/commands/swap.ts +349 -0
- package/src/cli/commands/wallet.ts +719 -0
- package/src/cli/index.ts +25 -0
- package/src/cli/quest/nara_quest.json +534 -0
- package/src/cli/quest/nara_quest_types.ts +540 -0
- package/src/cli/types.ts +207 -0
- package/src/cli/utils/output.ts +110 -0
- package/src/cli/utils/transaction.ts +146 -0
- package/src/cli/utils/validation.ts +120 -0
- package/src/cli/utils/wallet.ts +72 -0
- package/src/cli/zk/answer_proof.wasm +0 -0
- package/src/cli/zk/answer_proof_final.zkey +0 -0
- package/src/client.ts +96 -0
- package/src/config.ts +132 -0
- package/src/constants.ts +29 -0
- package/src/migrate.ts +222 -0
- package/src/pool.ts +259 -0
- package/src/quest.ts +379 -0
- package/src/swap.ts +608 -0
- package/src/types/snarkjs.d.ts +9 -0
package/src/cli/types.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-specific types and interfaces
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { PublicKey } from "@solana/web3.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Global options available on all commands
|
|
9
|
+
*/
|
|
10
|
+
export interface GlobalOptions {
|
|
11
|
+
/** RPC endpoint URL */
|
|
12
|
+
rpcUrl?: string;
|
|
13
|
+
/** Path to wallet keypair JSON file */
|
|
14
|
+
wallet?: string;
|
|
15
|
+
/** Output in JSON format */
|
|
16
|
+
json?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Config create command options
|
|
21
|
+
*/
|
|
22
|
+
export interface ConfigCreateOptions extends GlobalOptions {
|
|
23
|
+
/** Fee claimer wallet address */
|
|
24
|
+
feeClaimer?: string;
|
|
25
|
+
/** Leftover token receiver wallet address */
|
|
26
|
+
leftoverReceiver?: string;
|
|
27
|
+
/** Total token supply */
|
|
28
|
+
totalSupply?: number;
|
|
29
|
+
/** Initial market cap */
|
|
30
|
+
initialMcap?: number;
|
|
31
|
+
/** Migration market cap */
|
|
32
|
+
migrationMcap?: number;
|
|
33
|
+
/** Export unsigned transaction */
|
|
34
|
+
exportTx?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pool create command options
|
|
39
|
+
*/
|
|
40
|
+
export interface PoolCreateOptions extends GlobalOptions {
|
|
41
|
+
/** Token name */
|
|
42
|
+
name: string;
|
|
43
|
+
/** Token symbol */
|
|
44
|
+
symbol: string;
|
|
45
|
+
/** Metadata URI */
|
|
46
|
+
uri: string;
|
|
47
|
+
/** DBC config address */
|
|
48
|
+
dbcConfig: string;
|
|
49
|
+
/** Pool creator address */
|
|
50
|
+
creator?: string;
|
|
51
|
+
/** Export unsigned transaction */
|
|
52
|
+
exportTx?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Pool create with buy command options
|
|
57
|
+
*/
|
|
58
|
+
export interface PoolCreateWithBuyOptions extends PoolCreateOptions {
|
|
59
|
+
/** Initial buy amount in SOL */
|
|
60
|
+
amount: number;
|
|
61
|
+
/** Buyer address */
|
|
62
|
+
buyer?: string;
|
|
63
|
+
/** Token receiver address */
|
|
64
|
+
receiver?: string;
|
|
65
|
+
/** Slippage in basis points */
|
|
66
|
+
slippage?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Pool info command options
|
|
71
|
+
*/
|
|
72
|
+
export interface PoolInfoOptions extends GlobalOptions {
|
|
73
|
+
/** Token address */
|
|
74
|
+
tokenAddress: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Swap buy command options
|
|
79
|
+
*/
|
|
80
|
+
export interface SwapBuyOptions extends GlobalOptions {
|
|
81
|
+
/** Token address */
|
|
82
|
+
tokenAddress: string;
|
|
83
|
+
/** Amount in SOL */
|
|
84
|
+
amount: number;
|
|
85
|
+
/** Slippage in basis points */
|
|
86
|
+
slippage?: number;
|
|
87
|
+
/** Swap mode */
|
|
88
|
+
mode?: string;
|
|
89
|
+
/** Export unsigned transaction */
|
|
90
|
+
exportTx?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Swap sell command options
|
|
95
|
+
*/
|
|
96
|
+
export interface SwapSellOptions extends GlobalOptions {
|
|
97
|
+
/** Token address */
|
|
98
|
+
tokenAddress: string;
|
|
99
|
+
/** Amount in tokens */
|
|
100
|
+
amount: number;
|
|
101
|
+
/** Token decimals */
|
|
102
|
+
decimals?: number;
|
|
103
|
+
/** Slippage in basis points */
|
|
104
|
+
slippage?: number;
|
|
105
|
+
/** Swap mode */
|
|
106
|
+
mode?: string;
|
|
107
|
+
/** Export unsigned transaction */
|
|
108
|
+
exportTx?: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Swap quote command options
|
|
113
|
+
*/
|
|
114
|
+
export interface SwapQuoteOptions extends GlobalOptions {
|
|
115
|
+
/** Token address */
|
|
116
|
+
tokenAddress: string;
|
|
117
|
+
/** Amount */
|
|
118
|
+
amount: number;
|
|
119
|
+
/** Direction: buy or sell */
|
|
120
|
+
direction: string;
|
|
121
|
+
/** Token decimals (for sell only) */
|
|
122
|
+
decimals?: number;
|
|
123
|
+
/** Slippage in basis points */
|
|
124
|
+
slippage?: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Migrate launch command options
|
|
129
|
+
*/
|
|
130
|
+
export interface MigrateLaunchOptions extends GlobalOptions {
|
|
131
|
+
/** Token address */
|
|
132
|
+
tokenAddress: string;
|
|
133
|
+
/** Export unsigned transaction */
|
|
134
|
+
exportTx?: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Migrate create locker command options
|
|
139
|
+
*/
|
|
140
|
+
export interface MigrateCreateLockerOptions extends GlobalOptions {
|
|
141
|
+
/** Token address */
|
|
142
|
+
tokenAddress: string;
|
|
143
|
+
/** Export unsigned transaction */
|
|
144
|
+
exportTx?: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Migrate check command options
|
|
149
|
+
*/
|
|
150
|
+
export interface MigrateCheckOptions extends GlobalOptions {
|
|
151
|
+
/** Token address */
|
|
152
|
+
tokenAddress: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Wallet balance command options
|
|
157
|
+
*/
|
|
158
|
+
export interface WalletBalanceOptions extends GlobalOptions {
|
|
159
|
+
/** Wallet address (optional, defaults to current wallet) */
|
|
160
|
+
address?: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Token balance command options
|
|
165
|
+
*/
|
|
166
|
+
export interface TokenBalanceOptions extends GlobalOptions {
|
|
167
|
+
/** Token address */
|
|
168
|
+
tokenAddress: string;
|
|
169
|
+
/** Owner address (optional, defaults to current wallet) */
|
|
170
|
+
owner?: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Transaction status command options
|
|
175
|
+
*/
|
|
176
|
+
export interface TxStatusOptions extends GlobalOptions {
|
|
177
|
+
/** Transaction signature */
|
|
178
|
+
signature: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Transfer SOL command options
|
|
183
|
+
*/
|
|
184
|
+
export interface TransferSolOptions extends GlobalOptions {
|
|
185
|
+
/** Recipient address */
|
|
186
|
+
to: string;
|
|
187
|
+
/** Amount in SOL */
|
|
188
|
+
amount: number;
|
|
189
|
+
/** Export unsigned transaction */
|
|
190
|
+
exportTx?: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Transfer token command options
|
|
195
|
+
*/
|
|
196
|
+
export interface TransferTokenOptions extends GlobalOptions {
|
|
197
|
+
/** Token address */
|
|
198
|
+
tokenAddress: string;
|
|
199
|
+
/** Recipient address */
|
|
200
|
+
to: string;
|
|
201
|
+
/** Amount in tokens */
|
|
202
|
+
amount: number;
|
|
203
|
+
/** Token decimals */
|
|
204
|
+
decimals?: number;
|
|
205
|
+
/** Export unsigned transaction */
|
|
206
|
+
exportTx?: boolean;
|
|
207
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format output based on mode
|
|
7
|
+
* @param data Data to output
|
|
8
|
+
* @param jsonMode Whether to output in JSON format
|
|
9
|
+
*/
|
|
10
|
+
export function formatOutput(data: any, jsonMode: boolean = false): void {
|
|
11
|
+
if (jsonMode) {
|
|
12
|
+
console.log(JSON.stringify(data, null, 2));
|
|
13
|
+
} else {
|
|
14
|
+
// Human-readable output
|
|
15
|
+
formatHumanReadable(data);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Format data in human-readable format
|
|
21
|
+
* @param data Data to format
|
|
22
|
+
*/
|
|
23
|
+
function formatHumanReadable(data: any): void {
|
|
24
|
+
if (typeof data === "object" && data !== null) {
|
|
25
|
+
for (const [key, value] of Object.entries(data)) {
|
|
26
|
+
const label = formatLabel(key);
|
|
27
|
+
|
|
28
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
29
|
+
console.log(`\n${label}:`);
|
|
30
|
+
formatHumanReadable(value);
|
|
31
|
+
} else {
|
|
32
|
+
console.log(`${label}: ${formatValue(value)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
console.log(data);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Format object key as human-readable label
|
|
42
|
+
* @param key Object key
|
|
43
|
+
* @returns Formatted label
|
|
44
|
+
*/
|
|
45
|
+
function formatLabel(key: string): string {
|
|
46
|
+
// Convert camelCase to Title Case
|
|
47
|
+
const words = key.replace(/([A-Z])/g, " $1").trim();
|
|
48
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Format value for display
|
|
53
|
+
* @param value Value to format
|
|
54
|
+
* @returns Formatted value string
|
|
55
|
+
*/
|
|
56
|
+
function formatValue(value: any): string {
|
|
57
|
+
if (value === null || value === undefined) {
|
|
58
|
+
return "N/A";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof value === "boolean") {
|
|
62
|
+
return value ? "Yes" : "No";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof value === "number") {
|
|
66
|
+
// Format large numbers with commas
|
|
67
|
+
if (value >= 1000) {
|
|
68
|
+
return value.toLocaleString();
|
|
69
|
+
}
|
|
70
|
+
return value.toString();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
return value.join(", ");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return value.toString();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Print success message
|
|
82
|
+
* @param message Success message
|
|
83
|
+
*/
|
|
84
|
+
export function printSuccess(message: string): void {
|
|
85
|
+
console.log(`✅ ${message}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Print error message
|
|
90
|
+
* @param message Error message
|
|
91
|
+
*/
|
|
92
|
+
export function printError(message: string): void {
|
|
93
|
+
console.error(`❌ Error: ${message}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Print info message
|
|
98
|
+
* @param message Info message
|
|
99
|
+
*/
|
|
100
|
+
export function printInfo(message: string): void {
|
|
101
|
+
console.log(`ℹ️ ${message}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Print warning message
|
|
106
|
+
* @param message Warning message
|
|
107
|
+
*/
|
|
108
|
+
export function printWarning(message: string): void {
|
|
109
|
+
console.log(`⚠️ ${message}`);
|
|
110
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction handling utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
sendAndConfirmTransaction,
|
|
7
|
+
Transaction,
|
|
8
|
+
VersionedTransaction,
|
|
9
|
+
Keypair,
|
|
10
|
+
} from "@solana/web3.js";
|
|
11
|
+
import { NaraSDK } from "../../client";
|
|
12
|
+
import { printInfo, printSuccess } from "./output";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Result of transaction handling
|
|
16
|
+
*/
|
|
17
|
+
export interface TransactionResult {
|
|
18
|
+
/** Transaction signature (if sent) */
|
|
19
|
+
signature?: string;
|
|
20
|
+
/** Base64-encoded transaction (if exported) */
|
|
21
|
+
base64?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Handle transaction signing and sending or exporting
|
|
26
|
+
*
|
|
27
|
+
* @param sdk NaraSDK SDK instance
|
|
28
|
+
* @param transaction Transaction or VersionedTransaction
|
|
29
|
+
* @param signers Array of keypairs to sign with
|
|
30
|
+
* @param exportMode Whether to export unsigned transaction
|
|
31
|
+
* @returns Transaction result with signature or base64
|
|
32
|
+
*/
|
|
33
|
+
export async function handleTransaction(
|
|
34
|
+
sdk: NaraSDK,
|
|
35
|
+
transaction: Transaction | VersionedTransaction,
|
|
36
|
+
signers: Keypair[],
|
|
37
|
+
exportMode: boolean = false
|
|
38
|
+
): Promise<TransactionResult> {
|
|
39
|
+
if (exportMode) {
|
|
40
|
+
// Export unsigned transaction as base64
|
|
41
|
+
return exportTransaction(transaction);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Sign and send transaction
|
|
45
|
+
return await signAndSendTransaction(sdk, transaction, signers);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Export unsigned transaction as base64
|
|
50
|
+
* @param transaction Transaction to export
|
|
51
|
+
* @returns Base64-encoded transaction
|
|
52
|
+
*/
|
|
53
|
+
function exportTransaction(
|
|
54
|
+
transaction: Transaction | VersionedTransaction
|
|
55
|
+
): TransactionResult {
|
|
56
|
+
try {
|
|
57
|
+
const serialized = transaction.serialize({
|
|
58
|
+
requireAllSignatures: false,
|
|
59
|
+
verifySignatures: false,
|
|
60
|
+
});
|
|
61
|
+
const base64 = Buffer.from(serialized).toString("base64");
|
|
62
|
+
|
|
63
|
+
return { base64 };
|
|
64
|
+
} catch (error: any) {
|
|
65
|
+
throw new Error(`Failed to serialize transaction: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sign and send transaction
|
|
71
|
+
* @param sdk NaraSDK SDK instance
|
|
72
|
+
* @param transaction Transaction to sign and send
|
|
73
|
+
* @param signers Keypairs to sign with
|
|
74
|
+
* @returns Transaction signature
|
|
75
|
+
*/
|
|
76
|
+
async function signAndSendTransaction(
|
|
77
|
+
sdk: NaraSDK,
|
|
78
|
+
transaction: Transaction | VersionedTransaction,
|
|
79
|
+
signers: Keypair[]
|
|
80
|
+
): Promise<TransactionResult> {
|
|
81
|
+
const connection = sdk.getConnection();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
printInfo("Signing transaction...");
|
|
85
|
+
|
|
86
|
+
let signature: string;
|
|
87
|
+
|
|
88
|
+
if (transaction instanceof VersionedTransaction) {
|
|
89
|
+
// Handle VersionedTransaction
|
|
90
|
+
transaction.sign(signers);
|
|
91
|
+
|
|
92
|
+
printInfo("Sending transaction...");
|
|
93
|
+
signature = await connection.sendTransaction(transaction, {
|
|
94
|
+
maxRetries: 3,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
printInfo("Confirming transaction...");
|
|
98
|
+
await connection.confirmTransaction(signature, "confirmed");
|
|
99
|
+
} else {
|
|
100
|
+
// Handle regular Transaction
|
|
101
|
+
printInfo("Sending transaction...");
|
|
102
|
+
signature = await sendAndConfirmTransaction(
|
|
103
|
+
connection,
|
|
104
|
+
transaction,
|
|
105
|
+
signers,
|
|
106
|
+
{
|
|
107
|
+
commitment: "confirmed",
|
|
108
|
+
skipPreflight: true,
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { signature };
|
|
114
|
+
} catch (error: any) {
|
|
115
|
+
throw new Error(`Transaction failed: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Print transaction result
|
|
121
|
+
* @param result Transaction result
|
|
122
|
+
* @param jsonMode Whether to output in JSON format
|
|
123
|
+
*/
|
|
124
|
+
export function printTransactionResult(
|
|
125
|
+
result: TransactionResult,
|
|
126
|
+
jsonMode: boolean = false
|
|
127
|
+
): void {
|
|
128
|
+
if (result.signature) {
|
|
129
|
+
if (jsonMode) {
|
|
130
|
+
console.log(JSON.stringify({ signature: result.signature }, null, 2));
|
|
131
|
+
} else {
|
|
132
|
+
printSuccess("Transaction successful!");
|
|
133
|
+
console.log(`Signature: ${result.signature}`);
|
|
134
|
+
console.log(
|
|
135
|
+
`View on Solscan: https://solscan.io/tx/${result.signature}?cluster=devnet`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
} else if (result.base64) {
|
|
139
|
+
if (jsonMode) {
|
|
140
|
+
console.log(JSON.stringify({ transaction: result.base64 }, null, 2));
|
|
141
|
+
} else {
|
|
142
|
+
printSuccess("Transaction exported!");
|
|
143
|
+
console.log(`\nBase64 transaction:\n${result.base64}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { PublicKey } from "@solana/web3.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate that a string is a valid Solana public key
|
|
9
|
+
* @param address Address string to validate
|
|
10
|
+
* @returns PublicKey if valid
|
|
11
|
+
* @throws Error if invalid
|
|
12
|
+
*/
|
|
13
|
+
export function validatePublicKey(address: string): PublicKey {
|
|
14
|
+
try {
|
|
15
|
+
return new PublicKey(address);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
throw new Error(`Invalid Solana address: ${address}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate that a value is a positive number
|
|
23
|
+
* @param value Value to validate
|
|
24
|
+
* @param name Parameter name for error message
|
|
25
|
+
* @returns Parsed number if valid
|
|
26
|
+
* @throws Error if invalid
|
|
27
|
+
*/
|
|
28
|
+
export function validatePositiveNumber(
|
|
29
|
+
value: string | number,
|
|
30
|
+
name: string
|
|
31
|
+
): number {
|
|
32
|
+
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
33
|
+
|
|
34
|
+
if (isNaN(num)) {
|
|
35
|
+
throw new Error(`${name} must be a valid number`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (num <= 0) {
|
|
39
|
+
throw new Error(`${name} must be greater than 0`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return num;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validate that a value is a non-negative number
|
|
47
|
+
* @param value Value to validate
|
|
48
|
+
* @param name Parameter name for error message
|
|
49
|
+
* @returns Parsed number if valid
|
|
50
|
+
* @throws Error if invalid
|
|
51
|
+
*/
|
|
52
|
+
export function validateNonNegativeNumber(
|
|
53
|
+
value: string | number,
|
|
54
|
+
name: string
|
|
55
|
+
): number {
|
|
56
|
+
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
57
|
+
|
|
58
|
+
if (isNaN(num)) {
|
|
59
|
+
throw new Error(`${name} must be a valid number`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (num < 0) {
|
|
63
|
+
throw new Error(`${name} must be non-negative`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return num;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validate swap mode string
|
|
71
|
+
* @param mode Mode string
|
|
72
|
+
* @returns Validated mode string
|
|
73
|
+
* @throws Error if invalid
|
|
74
|
+
*/
|
|
75
|
+
export function validateSwapMode(mode: string): string {
|
|
76
|
+
const validModes = ["exact-in", "partial-fill", "exact-out"];
|
|
77
|
+
const normalized = mode.toLowerCase();
|
|
78
|
+
|
|
79
|
+
if (!validModes.includes(normalized)) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Invalid swap mode: ${mode}. Must be one of: ${validModes.join(", ")}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return normalized;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate direction string
|
|
90
|
+
* @param direction Direction string
|
|
91
|
+
* @returns Validated direction string
|
|
92
|
+
* @throws Error if invalid
|
|
93
|
+
*/
|
|
94
|
+
export function validateDirection(direction: string): string {
|
|
95
|
+
const validDirections = ["buy", "sell"];
|
|
96
|
+
const normalized = direction.toLowerCase();
|
|
97
|
+
|
|
98
|
+
if (!validDirections.includes(normalized)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Invalid direction: ${direction}. Must be one of: ${validDirections.join(
|
|
101
|
+
", "
|
|
102
|
+
)}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return normalized;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate required option
|
|
111
|
+
* @param value Option value
|
|
112
|
+
* @param name Option name
|
|
113
|
+
* @throws Error if value is undefined
|
|
114
|
+
*/
|
|
115
|
+
export function validateRequired<T>(value: T | undefined, name: string): T {
|
|
116
|
+
if (value === undefined || value === null) {
|
|
117
|
+
throw new Error(`${name} is required`);
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet loading utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Keypair } from "@solana/web3.js";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { DEFAULT_RPC_URL, DEFAULT_WALLET_PATH as _DEFAULT_WALLET_PATH } from "../../constants";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve wallet path (expand ~ to home directory)
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_WALLET_PATH = _DEFAULT_WALLET_PATH.startsWith("~")
|
|
14
|
+
? join(homedir(), _DEFAULT_WALLET_PATH.slice(1))
|
|
15
|
+
: _DEFAULT_WALLET_PATH;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load wallet keypair from file
|
|
19
|
+
*
|
|
20
|
+
* Priority:
|
|
21
|
+
* 1. CLI flag (walletPath parameter)
|
|
22
|
+
* 2. Default path (~/.config/nara/id.json)
|
|
23
|
+
* 3. Error if neither exists
|
|
24
|
+
*
|
|
25
|
+
* @param walletPath Optional path to wallet keypair JSON file
|
|
26
|
+
* @returns Keypair
|
|
27
|
+
* @throws Error if wallet cannot be loaded
|
|
28
|
+
*/
|
|
29
|
+
export async function loadWallet(walletPath?: string): Promise<Keypair> {
|
|
30
|
+
// Use provided path or default path
|
|
31
|
+
const path = walletPath || DEFAULT_WALLET_PATH;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const fs = await import("node:fs/promises");
|
|
35
|
+
const file = await fs.readFile(path, "utf-8");
|
|
36
|
+
const data = JSON.parse(file);
|
|
37
|
+
|
|
38
|
+
// Handle both array format [1,2,3,...] and object format
|
|
39
|
+
if (Array.isArray(data)) {
|
|
40
|
+
return Keypair.fromSecretKey(new Uint8Array(data));
|
|
41
|
+
} else if (data.secretKey) {
|
|
42
|
+
return Keypair.fromSecretKey(new Uint8Array(data.secretKey));
|
|
43
|
+
} else {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"Invalid wallet file format. Expected array or object with secretKey field."
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
} catch (error: any) {
|
|
49
|
+
if (!walletPath) {
|
|
50
|
+
// If using default path and it doesn't exist, provide helpful error message
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Wallet not found. Please create a wallet at ${DEFAULT_WALLET_PATH} or use --wallet flag to specify a different path.`
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error(`Failed to load wallet from ${path}: ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get RPC URL from options
|
|
62
|
+
*
|
|
63
|
+
* Priority:
|
|
64
|
+
* 1. CLI flag (rpcUrl parameter)
|
|
65
|
+
* 2. Default (from constants)
|
|
66
|
+
*
|
|
67
|
+
* @param rpcUrl Optional RPC URL from CLI flag
|
|
68
|
+
* @returns RPC URL
|
|
69
|
+
*/
|
|
70
|
+
export function getRpcUrl(rpcUrl?: string): string {
|
|
71
|
+
return rpcUrl || DEFAULT_RPC_URL;
|
|
72
|
+
}
|
|
Binary file
|
|
Binary file
|