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/src/client.ts ADDED
@@ -0,0 +1,96 @@
1
+ import {
2
+ Connection,
3
+ PublicKey,
4
+ Transaction,
5
+ AddressLookupTableAccount,
6
+ MessageV0,
7
+ VersionedTransaction,
8
+ } from "@solana/web3.js";
9
+ import { DynamicBondingCurveClient } from "@meteora-ag/dynamic-bonding-curve-sdk";
10
+
11
+ export interface NaraSDKConfig {
12
+ rpcUrl: string;
13
+ commitment?: "processed" | "confirmed" | "finalized";
14
+ /** Address Lookup Table addresses array for compressing transaction size */
15
+ addressLookupTableAddresses?: string[];
16
+ }
17
+
18
+ export class NaraSDK {
19
+ private connection: Connection;
20
+ private client: DynamicBondingCurveClient;
21
+ private addressLookupTableAddresses: PublicKey[];
22
+
23
+ constructor(config: NaraSDKConfig) {
24
+ this.connection = new Connection(
25
+ config.rpcUrl,
26
+ config.commitment || "confirmed"
27
+ );
28
+ this.client = new DynamicBondingCurveClient(
29
+ this.connection,
30
+ config.commitment || "confirmed"
31
+ );
32
+ this.addressLookupTableAddresses = (
33
+ config.addressLookupTableAddresses || []
34
+ ).map((addr) => new PublicKey(addr));
35
+ }
36
+
37
+ getConnection(): Connection {
38
+ return this.connection;
39
+ }
40
+
41
+ getClient(): DynamicBondingCurveClient {
42
+ return this.client;
43
+ }
44
+
45
+ getAddressLookupTableAddresses(): PublicKey[] {
46
+ return this.addressLookupTableAddresses;
47
+ }
48
+
49
+ /**
50
+ * Compile transaction with Address Lookup Tables
51
+ * Converts Transaction to VersionedTransaction if ALT is configured
52
+ * @param transaction Original transaction
53
+ * @param feePayer Fee payer public key
54
+ * @returns VersionedTransaction or original Transaction
55
+ */
56
+ async compileTransactionWithALT(
57
+ transaction: Transaction,
58
+ feePayer: PublicKey
59
+ ): Promise<Transaction | VersionedTransaction> {
60
+ if (this.addressLookupTableAddresses.length === 0) {
61
+ // No ALT configured, return original transaction
62
+ return transaction;
63
+ }
64
+
65
+ // Fetch Address Lookup Table accounts
66
+ const lookupTableAccounts: AddressLookupTableAccount[] = [];
67
+ for (const address of this.addressLookupTableAddresses) {
68
+ const accountInfo =
69
+ await this.connection.getAddressLookupTable(address);
70
+ if (accountInfo.value) {
71
+ lookupTableAccounts.push(accountInfo.value);
72
+ }
73
+ }
74
+
75
+ if (lookupTableAccounts.length === 0) {
76
+ // No valid ALT accounts, return original transaction
77
+ return transaction;
78
+ }
79
+
80
+ // Get latest blockhash
81
+ const { blockhash } = await this.connection.getLatestBlockhash(
82
+ this.connection.commitment
83
+ );
84
+
85
+ // Create MessageV0
86
+ const messageV0 = MessageV0.compile({
87
+ payerKey: feePayer,
88
+ instructions: transaction.instructions,
89
+ recentBlockhash: blockhash,
90
+ addressLookupTableAccounts: lookupTableAccounts,
91
+ });
92
+
93
+ // Create VersionedTransaction
94
+ return new VersionedTransaction(messageV0);
95
+ }
96
+ }
package/src/config.ts ADDED
@@ -0,0 +1,132 @@
1
+ import { Keypair, PublicKey, Transaction } from "@solana/web3.js";
2
+ import {
3
+ buildCurveWithMarketCap,
4
+ ActivationType,
5
+ CollectFeeMode,
6
+ BaseFeeMode,
7
+ MigrationFeeOption,
8
+ MigrationOption,
9
+ TokenDecimal,
10
+ TokenType,
11
+ TokenUpdateAuthorityOption,
12
+ } from "@meteora-ag/dynamic-bonding-curve-sdk";
13
+ import { NATIVE_MINT } from "@solana/spl-token";
14
+ import { NaraSDK } from "./client";
15
+
16
+ /**
17
+ * Options for creating configuration
18
+ */
19
+ export interface CreateConfigOptions {
20
+ /** Fee claimer wallet address */
21
+ feeClaimer: PublicKey;
22
+ /** Leftover token receiver wallet address */
23
+ leftoverReceiver: PublicKey;
24
+ /** Payer wallet address */
25
+ payer: PublicKey;
26
+ // Curve parameters (all optional with defaults)
27
+ /** Total token supply (default: 1,000,000,000) */
28
+ totalTokenSupply?: number;
29
+ /** Initial market cap (default: 30) */
30
+ initialMarketCap?: number;
31
+ /** Migration market cap threshold (default: 540) */
32
+ migrationMarketCap?: number;
33
+ }
34
+
35
+ /**
36
+ * Return type for bonding curve config transaction creation
37
+ */
38
+ export interface CreateConfigResult {
39
+ /** Config address */
40
+ configAddress: string;
41
+ /** Unsigned transaction */
42
+ transaction: Transaction;
43
+ /** Config keypair (requires partial signature) */
44
+ configKeypair: Keypair;
45
+ }
46
+
47
+ /**
48
+ * Create bonding curve config transaction (returns unsigned transaction)
49
+ * @param sdk NaraSDK SDK instance
50
+ * @param options Configuration options
51
+ * @returns Config address, unsigned transaction, and config keypair
52
+ */
53
+ export async function createConfig(
54
+ sdk: NaraSDK,
55
+ options: CreateConfigOptions
56
+ ): Promise<CreateConfigResult> {
57
+ const connection = sdk.getConnection();
58
+ const client = sdk.getClient();
59
+
60
+ // Generate new config keypair
61
+ const config = Keypair.generate();
62
+
63
+ // Build bonding curve configuration
64
+ const curveConfig = buildCurveWithMarketCap({
65
+ totalTokenSupply: options.totalTokenSupply ?? 1_000_000_000, // Total token supply
66
+ initialMarketCap: options.initialMarketCap ?? 30, // Initial market cap
67
+ migrationMarketCap: options.migrationMarketCap ?? 540, // Migration market cap threshold
68
+ migrationOption: MigrationOption.MET_DAMM_V2, // Migration option: use Meteora DAMM V2
69
+ tokenBaseDecimal: TokenDecimal.SIX, // Base token decimals: 6
70
+ tokenQuoteDecimal: TokenDecimal.NINE, // Quote token decimals: 9 (SOL)
71
+ // Locked vesting parameters
72
+ lockedVestingParams: {
73
+ totalLockedVestingAmount: 0, // Total locked vesting amount
74
+ numberOfVestingPeriod: 0, // Number of vesting periods
75
+ cliffUnlockAmount: 0, // Cliff unlock amount
76
+ totalVestingDuration: 0, // Total vesting duration
77
+ cliffDurationFromMigrationTime: 0, // Cliff duration from migration time
78
+ },
79
+ // Base fee parameters
80
+ baseFeeParams: {
81
+ baseFeeMode: BaseFeeMode.FeeSchedulerLinear, // Fee mode: linear scheduler
82
+ feeSchedulerParam: {
83
+ startingFeeBps: 100, // Starting fee (basis points): 1%
84
+ endingFeeBps: 100, // Ending fee (basis points): 1%
85
+ numberOfPeriod: 0, // Number of periods
86
+ totalDuration: 0, // Total duration
87
+ },
88
+ },
89
+ dynamicFeeEnabled: true, // Enable dynamic fees
90
+ activationType: ActivationType.Slot, // Activation type: slot-based
91
+ collectFeeMode: CollectFeeMode.QuoteToken, // Fee collection mode: quote token
92
+ migrationFeeOption: MigrationFeeOption.FixedBps25, // Migration fee option: fixed 1%
93
+ tokenType: TokenType.SPL, // Token type: SPL token
94
+ partnerLiquidityPercentage: 0, // Partner liquidity percentage
95
+ creatorLiquidityPercentage: 0, // Creator liquidity percentage
96
+ partnerPermanentLockedLiquidityPercentage: 100, // Partner permanent locked liquidity: 100%
97
+ creatorPermanentLockedLiquidityPercentage: 0, // Creator permanent locked liquidity: 0%
98
+ creatorTradingFeePercentage: 0, // Creator trading fee percentage: 0%
99
+ leftover: 0, // Leftover token amount
100
+ tokenUpdateAuthority: TokenUpdateAuthorityOption.Immutable, // Token update authority: immutable
101
+ // Migration fee
102
+ migrationFee: {
103
+ feePercentage: 0, // Fee percentage
104
+ creatorFeePercentage: 0, // Creator fee percentage
105
+ },
106
+ poolCreationFee: 0.1, // Pool creation fee
107
+ enableFirstSwapWithMinFee: true, // Enable first swap with minimum fee
108
+ });
109
+
110
+ // Create config transaction
111
+ const transaction = await client.partner.createConfig({
112
+ config: config.publicKey, // Config public key
113
+ feeClaimer: options.feeClaimer, // Fee claimer
114
+ leftoverReceiver: options.leftoverReceiver, // Leftover receiver
115
+ payer: options.payer, // Payer
116
+ quoteMint: NATIVE_MINT, // Quote mint: native SOL
117
+ ...curveConfig, // Curve config parameters
118
+ });
119
+
120
+ // Get latest blockhash
121
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
122
+ transaction.recentBlockhash = blockhash;
123
+ transaction.feePayer = options.payer;
124
+ // Config account partial signature
125
+ transaction.partialSign(config);
126
+
127
+ return {
128
+ configAddress: config.publicKey.toBase58(), // Config address
129
+ transaction, // Unsigned transaction (already has config's partial signature)
130
+ configKeypair: config, // Config keypair
131
+ };
132
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * SDK and CLI default constants
3
+ *
4
+ * Priority for all values: CLI flag > env variable > default value
5
+ */
6
+
7
+ /**
8
+ * Default RPC URL for Nara testnet
9
+ */
10
+ export const DEFAULT_RPC_URL =
11
+ process.env.RPC_URL || "https://mainnet-api.nara.build/";
12
+
13
+ /**
14
+ * Default bonding curve config address
15
+ */
16
+ export const DEFAULT_DBC_CONFIG_ADDRESS =
17
+ process.env.DBC_CONFIG_ADDRESS || "";
18
+
19
+ /**
20
+ * Default wallet path
21
+ */
22
+ export const DEFAULT_WALLET_PATH =
23
+ process.env.WALLET_PATH || "~/.config/nara/id.json";
24
+
25
+ /**
26
+ * Default quest relay URL
27
+ */
28
+ export const DEFAULT_QUEST_RELAY_URL =
29
+ process.env.QUEST_RELAY_URL || "https://quest-api.nara.build/";
package/src/migrate.ts ADDED
@@ -0,0 +1,222 @@
1
+ import {
2
+ PublicKey,
3
+ Transaction,
4
+ Keypair,
5
+ VersionedTransaction,
6
+ } from "@solana/web3.js";
7
+ import BN from "bn.js";
8
+ import { NaraSDK } from "./client";
9
+ import { DAMM_V2_MIGRATION_FEE_ADDRESS } from "@meteora-ag/dynamic-bonding-curve-sdk";
10
+
11
+ export interface MigrateToDAMMV2Params {
12
+ /** Token address (baseMint) */
13
+ tokenAddress: string;
14
+ /** Payer */
15
+ payer: PublicKey;
16
+ }
17
+
18
+ export interface MigrateToDAMMV2Result {
19
+ /** Migration transaction (returns VersionedTransaction if ALT is configured) */
20
+ transaction: Transaction | VersionedTransaction;
21
+ /** First Position NFT keypair (requires signature) */
22
+ firstPositionNftKeypair: Keypair;
23
+ /** Second Position NFT keypair (requires signature) */
24
+ secondPositionNftKeypair: Keypair;
25
+ /** Pool address */
26
+ poolAddress: string;
27
+ }
28
+
29
+ export interface CreateLockerParams {
30
+ /** Token address (baseMint) */
31
+ tokenAddress: string;
32
+ /** Payer */
33
+ payer: PublicKey;
34
+ }
35
+
36
+ export interface CreateLockerResult {
37
+ /** Locker creation transaction (returns VersionedTransaction if ALT is configured) */
38
+ transaction: Transaction | VersionedTransaction;
39
+ /** Pool address */
40
+ poolAddress: string;
41
+ }
42
+
43
+ /**
44
+ * Launch token pool to DAMM V2 (graduation)
45
+ *
46
+ * Call this function to migrate the pool to DAMM V2 after the bonding curve is complete (100%)
47
+ *
48
+ * Notes:
49
+ * - dammConfig address is automatically derived from pool config's migrationFeeOption
50
+ * - If token has locked vesting parameters, may need to call createLocker() first
51
+ * - Requires signatures from three keypairs: payer, firstPositionNftKeypair, secondPositionNftKeypair
52
+ *
53
+ * @param sdk NaraSDK SDK instance
54
+ * @param params Migration parameters
55
+ * @returns Migration transaction, Position NFT keypairs, and pool address
56
+ */
57
+ export async function migrateToDAMMV2(
58
+ sdk: NaraSDK,
59
+ params: MigrateToDAMMV2Params
60
+ ): Promise<MigrateToDAMMV2Result> {
61
+ const connection = sdk.getConnection();
62
+ const client = sdk.getClient();
63
+ const tokenPubkey = new PublicKey(params.tokenAddress);
64
+
65
+ // Get pool account
66
+ const poolAccount = await client.state.getPoolByBaseMint(tokenPubkey);
67
+ if (!poolAccount) {
68
+ throw new Error(`Pool not found for token: ${params.tokenAddress}`);
69
+ }
70
+
71
+ // Check if pool has already been migrated
72
+ if (poolAccount.account.isMigrated) {
73
+ throw new Error("Pool has already been migrated");
74
+ }
75
+
76
+ // Get pool config and read migrationFeeOption
77
+ const virtualPool = poolAccount.account;
78
+ const poolConfig = await client.state.getPoolConfig(virtualPool.config);
79
+
80
+ // Get corresponding config address from DAMM_V2_MIGRATION_FEE_ADDRESS array
81
+ // Using 'any' type since poolConfig's migrationFeeOption is IDL-derived
82
+ const migrationFeeOption = (poolConfig as any).migrationFeeOption || 0;
83
+ const dammConfig = DAMM_V2_MIGRATION_FEE_ADDRESS[migrationFeeOption];
84
+
85
+ if (!dammConfig) {
86
+ throw new Error(
87
+ `Invalid migration fee option: ${migrationFeeOption}. Cannot determine DAMM config address.`
88
+ );
89
+ }
90
+
91
+ // Note: If the pool has locked vesting parameters, a locker might need to be created first
92
+ // Use createLocker() if the migration fails due to missing locker
93
+
94
+ // Call SDK's migrateToDammV2 method
95
+ const result = await client.migration.migrateToDammV2({
96
+ payer: params.payer,
97
+ virtualPool: poolAccount.publicKey,
98
+ dammConfig,
99
+ });
100
+
101
+ // Get latest blockhash
102
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
103
+ result.transaction.recentBlockhash = blockhash;
104
+ result.transaction.feePayer = params.payer;
105
+
106
+ // Compile transaction with ALT if configured
107
+ const compiledTx = await sdk.compileTransactionWithALT(
108
+ result.transaction,
109
+ params.payer
110
+ );
111
+
112
+ return {
113
+ transaction: compiledTx,
114
+ firstPositionNftKeypair: result.firstPositionNftKeypair,
115
+ secondPositionNftKeypair: result.secondPositionNftKeypair,
116
+ poolAddress: poolAccount.publicKey.toBase58(),
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Create Locker (for token pools with locked vesting parameters)
122
+ *
123
+ * If the token pool has locked vesting parameters (amountPerPeriod > 0 or cliffUnlockAmount > 0),
124
+ * a locker must be created before migrating to DAMM V2
125
+ *
126
+ * @param sdk NaraSDK SDK instance
127
+ * @param params Locker parameters
128
+ * @returns Locker creation transaction and pool address
129
+ */
130
+ export async function createLocker(
131
+ sdk: NaraSDK,
132
+ params: CreateLockerParams
133
+ ): Promise<CreateLockerResult> {
134
+ const connection = sdk.getConnection();
135
+ const client = sdk.getClient();
136
+ const tokenPubkey = new PublicKey(params.tokenAddress);
137
+
138
+ // Get pool account
139
+ const poolAccount = await client.state.getPoolByBaseMint(tokenPubkey);
140
+ if (!poolAccount) {
141
+ throw new Error(`Pool not found for token: ${params.tokenAddress}`);
142
+ }
143
+
144
+ // Create locker
145
+ const transaction = await client.migration.createLocker({
146
+ payer: params.payer,
147
+ virtualPool: poolAccount.publicKey,
148
+ });
149
+
150
+ // Get latest blockhash
151
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
152
+ transaction.recentBlockhash = blockhash;
153
+ transaction.feePayer = params.payer;
154
+
155
+ // Compile transaction with ALT if configured
156
+ const compiledTx = await sdk.compileTransactionWithALT(
157
+ transaction,
158
+ params.payer
159
+ );
160
+
161
+ return {
162
+ transaction: compiledTx,
163
+ poolAddress: poolAccount.publicKey.toBase58(),
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Check if pool can be launched to DAMM V2
169
+ *
170
+ * @param sdk NaraSDK SDK instance
171
+ * @param tokenAddress Token address (baseMint)
172
+ * @returns Whether pool can be launched
173
+ */
174
+ export async function canMigrate(
175
+ sdk: NaraSDK,
176
+ tokenAddress: string
177
+ ): Promise<{
178
+ canMigrate: boolean;
179
+ reason?: string;
180
+ progress: number;
181
+ }> {
182
+ const client = sdk.getClient();
183
+ const tokenPubkey = new PublicKey(tokenAddress);
184
+
185
+ // Get pool account
186
+ const poolAccount = await client.state.getPoolByBaseMint(tokenPubkey);
187
+ if (!poolAccount) {
188
+ return {
189
+ canMigrate: false,
190
+ reason: "Pool not found",
191
+ progress: 0,
192
+ };
193
+ }
194
+
195
+ // Check if already migrated
196
+ if (poolAccount.account.isMigrated) {
197
+ return {
198
+ canMigrate: false,
199
+ reason: "Pool has already been migrated",
200
+ progress: 1,
201
+ };
202
+ }
203
+
204
+ // Get curve progress
205
+ const progress = await client.state.getPoolCurveProgress(
206
+ poolAccount.publicKey
207
+ );
208
+
209
+ // Check if 100% complete
210
+ if (progress >= 1.0) {
211
+ return {
212
+ canMigrate: true,
213
+ progress,
214
+ };
215
+ }
216
+
217
+ return {
218
+ canMigrate: false,
219
+ reason: `Curve not complete. Current progress: ${(progress * 100).toFixed(2)}%`,
220
+ progress,
221
+ };
222
+ }