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.
@@ -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