@forgedevv/core 0.1.0 → 0.2.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/dist/swap.d.ts CHANGED
@@ -27,9 +27,14 @@
27
27
  * Refusals (typed `SwapError`, never a silent fallback):
28
28
  * - the pool is migrated or its curve is complete (`POOL_GRADUATED`): the template trades through
29
29
  * Jupiter in that case;
30
+ * - the pool's config is not in the caller's `allowedConfigs` (`POOL_NOT_ALLOWED`): a launchpad
31
+ * site only trades its own coins;
30
32
  * - the platform fee wallet does not exist or is not rent-exempt
31
33
  * (`PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT`): the whole swap would fail on-chain (devnet finding);
32
- * - the referral account is not an initialized wrapped SOL token account (`REFERRAL_ACCOUNT_INVALID`);
34
+ * - the platform fee wallet is not exactly `FORGE_PLATFORM_FEE_WALLET`, or not a system account
35
+ * (`PLATFORM_FEE_WALLET_MISMATCH`);
36
+ * - the referral account is not an initialized wrapped SOL token account, or is not held by
37
+ * `FORGE_MULTISIG_VAULT` when the environment has it (`REFERRAL_ACCOUNT_INVALID`);
33
38
  * - the pool is not a plain SOL-quoted DBC pool (`UNSUPPORTED_POOL`; transfer-hook pools excluded);
34
39
  * - the serialized transaction exceeds 1,232 bytes (`TRANSACTION_TOO_LARGE`).
35
40
  *
@@ -50,8 +55,8 @@ import { type PoolConfig, type VirtualPool } from '@meteora-ag/dynamic-bonding-c
50
55
  import { type AddressEnv } from './addresses.js';
51
56
  /** Platform fee on swaps built by @forge/core, in bps (`FEES.platformFeeBps` from @forge/shared). */
52
57
  export declare const PLATFORM_FEE_BPS: number;
53
- /** Highest slippage tolerance accepted by the builders (50%), whether given in bps or as a minimum. */
54
- export declare const MAX_SWAP_SLIPPAGE_BPS = 5000;
58
+ /** Highest slippage tolerance accepted by the builders (25%), whether given in bps or as a minimum. */
59
+ export declare const MAX_SWAP_SLIPPAGE_BPS = 2500;
55
60
  /**
56
61
  * Default compute unit limit. Measured on devnet: ~51,000 CU for a buy (~66,500 when it also creates
57
62
  * the coin ATA), ~49,000 CU for a sell; the default leaves about 2x headroom (fee scheduler,
@@ -66,7 +71,7 @@ export declare const MAX_SWAP_PRIORITY_FEE_MICRO_LAMPORTS = 10000000;
66
71
  export declare const SWAP_BASE_FEE_LAMPORTS_PER_SIGNATURE = 5000n;
67
72
  /** Serialized transaction size limit (`PACKET_DATA_SIZE` = 1,232 bytes). */
68
73
  export declare const SWAP_MAX_TRANSACTION_BYTES: number;
69
- export declare const SWAP_ERROR_CODES: readonly ["INVALID_PARAMS", "POOL_NOT_FOUND", "UNSUPPORTED_POOL", "POOL_GRADUATED", "PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT", "REFERRAL_ACCOUNT_INVALID", "INSUFFICIENT_LIQUIDITY", "QUOTE_FAILED", "AMOUNT_TOO_SMALL", "MIN_AMOUNT_OUT_ABOVE_QUOTE", "SLIPPAGE_TOO_HIGH", "INSUFFICIENT_SOL_BALANCE", "INSUFFICIENT_TOKEN_BALANCE", "TRANSACTION_TOO_LARGE", "RPC_ERROR"];
74
+ export declare const SWAP_ERROR_CODES: readonly ["INVALID_PARAMS", "POOL_NOT_FOUND", "UNSUPPORTED_POOL", "POOL_GRADUATED", "POOL_NOT_ALLOWED", "PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT", "PLATFORM_FEE_WALLET_MISMATCH", "REFERRAL_ACCOUNT_INVALID", "INSUFFICIENT_LIQUIDITY", "QUOTE_FAILED", "AMOUNT_TOO_SMALL", "MIN_AMOUNT_OUT_ABOVE_QUOTE", "SLIPPAGE_TOO_HIGH", "INSUFFICIENT_SOL_BALANCE", "INSUFFICIENT_TOKEN_BALANCE", "TRANSACTION_TOO_LARGE", "RPC_ERROR"];
70
75
  export type SwapErrorCode = (typeof SWAP_ERROR_CODES)[number];
71
76
  /** Public data attached to a `SwapError` (addresses, amounts as decimal strings). Never secrets. */
72
77
  export type SwapErrorDetails = Readonly<Record<string, string | number | boolean>>;
@@ -270,10 +275,31 @@ export interface SwapContext extends SwapPoolState {
270
275
  recentBlockhash: string;
271
276
  lastValidBlockHeight: number;
272
277
  }
273
- /** Throws unless the platform fee wallet exists and holds at least its rent-exempt minimum. */
274
- export declare function assertPlatformFeeWalletReady(wallet: PublicKey, account: SwapAccountSnapshot | null, rentExemptMinimum: bigint): void;
275
- /** Throws unless the referral account is an initialized wrapped SOL token account (SPL Token). */
276
- export declare function assertReferralAccountReady(address: PublicKey, account: SwapAccountSnapshot | null): void;
278
+ /**
279
+ * Throws unless the platform fee wallet exists and holds at least its rent-exempt minimum. With
280
+ * `expectedWallet` (FORGE_PLATFORM_FEE_WALLET, as `buildBuyTx` / `buildSellTx` pass it), the
281
+ * wallet must be exactly that key and a plain system account: lamports sent to an account owned by
282
+ * another program could not be spent by FORGE (`PLATFORM_FEE_WALLET_MISMATCH`).
283
+ */
284
+ export declare function assertPlatformFeeWalletReady(wallet: PublicKey, account: SwapAccountSnapshot | null, rentExemptMinimum: bigint, expectedWallet?: PublicKey | null): void;
285
+ /**
286
+ * Throws unless the referral account is an initialized wrapped SOL token account (SPL Token).
287
+ * With `expectedOwner` (FORGE_MULTISIG_VAULT, when the environment has it), the token account must
288
+ * be held by that key: otherwise the referral share would be paid to someone else.
289
+ */
290
+ export declare function assertReferralAccountReady(address: PublicKey, account: SwapAccountSnapshot | null, expectedOwner?: PublicKey | null): void;
291
+ /** The FORGE fee accounts a swap transaction must pay, as the builders read them from the env. */
292
+ export interface SwapFeeAccountExpectations {
293
+ /** The platform fee transfer must go to exactly this key (FORGE_PLATFORM_FEE_WALLET). */
294
+ platformFeeWallet?: PublicKey | null;
295
+ /** The Meteora referral token account must be held by this key (FORGE_MULTISIG_VAULT). */
296
+ referralOwner?: PublicKey | null;
297
+ }
298
+ /**
299
+ * Throws `POOL_NOT_ALLOWED` unless the pool's config is one of `allowedConfigs`. Without a list
300
+ * (`undefined`), any config is accepted; an empty list accepts none.
301
+ */
302
+ export declare function assertPoolConfigAllowed(pool: PublicKey, config: PublicKey, allowedConfigs: readonly (PublicKey | string)[] | undefined): void;
277
303
  export interface SwapBuildOptions {
278
304
  /** Compute unit limit (default `DEFAULT_SWAP_COMPUTE_UNIT_LIMIT`). */
279
305
  computeUnitLimit?: number;
@@ -321,12 +347,19 @@ export declare function compileSwapTransaction(args: {
321
347
  sizeBytes: number;
322
348
  };
323
349
  /**
324
- * Pure transaction builder: validates the context (pool on its curve, fee accounts ready, balances),
325
- * quotes, and assembles the instructions in the order documented at the top of this file.
350
+ * Pure transaction builder: validates the context (pool on its curve, fee accounts ready and, with
351
+ * `expected`, exactly FORGE's), quotes, and assembles the instructions in the order documented at
352
+ * the top of this file.
326
353
  */
327
- export declare function buildSwapTransaction(ctx: SwapContext, request: SwapBuildRequest): BuiltSwapTransaction;
328
- /** Reads and decodes a pool and its config, plus the current point. */
329
- export declare function fetchSwapPoolState(connection: Connection, poolAddress: PublicKey | string, commitment?: Commitment): Promise<SwapPoolState & {
354
+ export declare function buildSwapTransaction(ctx: SwapContext, request: SwapBuildRequest, expected?: SwapFeeAccountExpectations): BuiltSwapTransaction;
355
+ /**
356
+ * Reads and decodes a pool and its config, plus the current point. With
357
+ * `options.allowedConfigs`, a pool whose config is not in the list is refused (`POOL_NOT_ALLOWED`)
358
+ * before its config is read.
359
+ */
360
+ export declare function fetchSwapPoolState(connection: Connection, poolAddress: PublicKey | string, commitment?: Commitment, options?: {
361
+ allowedConfigs?: readonly (PublicKey | string)[];
362
+ }): Promise<SwapPoolState & {
330
363
  configAddress: PublicKey;
331
364
  }>;
332
365
  export interface FetchSwapContextParams {
@@ -336,22 +369,39 @@ export interface FetchSwapContextParams {
336
369
  platformFeeWallet: PublicKey;
337
370
  referralTokenAccount: PublicKey;
338
371
  commitment?: Commitment;
372
+ /** Configs the pool may belong to; anything else is `POOL_NOT_ALLOWED` (default: any config). */
373
+ allowedConfigs?: readonly (PublicKey | string)[];
339
374
  }
340
375
  /** All network reads of a swap build: pool, config, user accounts, fee accounts, rent, blockhash. */
341
376
  export declare function fetchSwapContext(p: FetchSwapContextParams): Promise<SwapContext>;
342
- /** FORGE fee accounts used by the swap, resolved through addresses.ts (never hard-coded). */
377
+ /**
378
+ * FORGE fee accounts used by the swap, resolved through addresses.ts (never hard-coded), and the
379
+ * multisig vault that must hold the referral account (`null` when the environment does not have
380
+ * `FORGE_MULTISIG_VAULT`: the owner check is then skipped).
381
+ */
343
382
  export declare function swapFeeAddresses(env?: AddressEnv): {
344
383
  platformFeeWallet: PublicKey;
345
384
  referralTokenAccount: PublicKey;
385
+ multisigVault: PublicKey | null;
346
386
  };
347
387
  interface SwapCommonParams extends SwapBuildOptions {
348
388
  connection: Connection;
349
389
  pool: PublicKey | string;
350
390
  /** The trader: fee payer, signer and owner of the token accounts. */
351
391
  user: PublicKey | string;
352
- /** Environment to read the FORGE addresses from (defaults to `process.env`). */
392
+ /**
393
+ * Environment to read the FORGE addresses from (defaults to `process.env`):
394
+ * `FORGE_PLATFORM_FEE_WALLET` and `FORGE_METEORA_REFERRAL_ACCOUNT` (required), and
395
+ * `FORGE_MULTISIG_VAULT` (optional: when set, the referral account must be held by it).
396
+ */
353
397
  env?: AddressEnv;
354
398
  commitment?: Commitment;
399
+ /**
400
+ * Configs the pool must belong to (e.g. the two configs of the launchpad whose site builds the
401
+ * swap). The pool's config is read on-chain; any other pool is refused with `POOL_NOT_ALLOWED`.
402
+ * Without it, any SOL-quoted DBC pool is accepted.
403
+ */
404
+ allowedConfigs?: readonly (PublicKey | string)[];
355
405
  }
356
406
  export type BuildBuyTxParams = SwapCommonParams & {
357
407
  /** SOL into the swap, in lamports. The platform fee is paid on top of it. */
@@ -372,6 +422,8 @@ export type QuoteSwapParams = {
372
422
  /** Lamports on a buy, raw coin units on a sell. */
373
423
  amountIn: bigint;
374
424
  commitment?: Commitment;
425
+ /** Same as on `buildBuyTx`: configs the pool must belong to (`POOL_NOT_ALLOWED` otherwise). */
426
+ allowedConfigs?: readonly (PublicKey | string)[];
375
427
  } & (SlippageInput | {
376
428
  slippageBps?: undefined;
377
429
  minAmountOut?: undefined;
@@ -382,8 +434,9 @@ export type QuoteSwapParams = {
382
434
  */
383
435
  export declare function quoteSwap(params: QuoteSwapParams): Promise<SwapQuote>;
384
436
  /**
385
- * Startup / pre-build check of the FORGE fee accounts: the platform fee wallet must be rent-exempt
386
- * and the referral account must be a wrapped SOL token account. Throws `SwapError`.
437
+ * Startup / pre-build check of the FORGE fee accounts: the platform fee wallet must be a
438
+ * rent-exempt system account and the referral account must be a wrapped SOL token account, held by
439
+ * `FORGE_MULTISIG_VAULT` when the environment has it. Throws `SwapError`.
387
440
  */
388
441
  export declare function assertSwapFeeAccountsReady(params: {
389
442
  connection: Connection;
package/dist/swap.js CHANGED
@@ -27,9 +27,14 @@
27
27
  * Refusals (typed `SwapError`, never a silent fallback):
28
28
  * - the pool is migrated or its curve is complete (`POOL_GRADUATED`): the template trades through
29
29
  * Jupiter in that case;
30
+ * - the pool's config is not in the caller's `allowedConfigs` (`POOL_NOT_ALLOWED`): a launchpad
31
+ * site only trades its own coins;
30
32
  * - the platform fee wallet does not exist or is not rent-exempt
31
33
  * (`PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT`): the whole swap would fail on-chain (devnet finding);
32
- * - the referral account is not an initialized wrapped SOL token account (`REFERRAL_ACCOUNT_INVALID`);
34
+ * - the platform fee wallet is not exactly `FORGE_PLATFORM_FEE_WALLET`, or not a system account
35
+ * (`PLATFORM_FEE_WALLET_MISMATCH`);
36
+ * - the referral account is not an initialized wrapped SOL token account, or is not held by
37
+ * `FORGE_MULTISIG_VAULT` when the environment has it (`REFERRAL_ACCOUNT_INVALID`);
33
38
  * - the pool is not a plain SOL-quoted DBC pool (`UNSUPPORTED_POOL`; transfer-hook pools excluded);
34
39
  * - the serialized transaction exceeds 1,232 bytes (`TRANSACTION_TOO_LARGE`).
35
40
  *
@@ -48,15 +53,15 @@ import { ComputeBudgetProgram, PACKET_DATA_SIZE, PublicKey, SYSVAR_INSTRUCTIONS_
48
53
  import { ACCOUNT_SIZE, ExtensionType, NATIVE_MINT, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, createCloseAccountInstruction, createSyncNativeInstruction, getAccountLen, getAssociatedTokenAddressSync, unpackAccount, } from '@solana/spl-token';
49
54
  import BN from 'bn.js';
50
55
  import { ActivationType, BaseFeeMode, DYNAMIC_BONDING_CURVE_PROGRAM_ID, DynamicBondingCurveIdl, TradeDirection, createDbcProgram, deriveDbcEventAuthority, deriveDbcPoolAuthority, getFeeMode, getTokenProgram, isRateLimiterApplied, swapQuote, } from '@meteora-ag/dynamic-bonding-curve-sdk';
51
- import { requireAddresses } from './addresses.js';
56
+ import { readOptionalAddress, requireAddresses } from './addresses.js';
52
57
  import { FEES } from './constants.js';
53
58
  // ---------------------------------------------------------------------------------------------
54
59
  // Constants
55
60
  // ---------------------------------------------------------------------------------------------
56
61
  /** Platform fee on swaps built by @forge/core, in bps (`FEES.platformFeeBps` from @forge/shared). */
57
62
  export const PLATFORM_FEE_BPS = FEES.platformFeeBps;
58
- /** Highest slippage tolerance accepted by the builders (50%), whether given in bps or as a minimum. */
59
- export const MAX_SWAP_SLIPPAGE_BPS = 5_000;
63
+ /** Highest slippage tolerance accepted by the builders (25%), whether given in bps or as a minimum. */
64
+ export const MAX_SWAP_SLIPPAGE_BPS = 2_500;
60
65
  /**
61
66
  * Default compute unit limit. Measured on devnet: ~51,000 CU for a buy (~66,500 when it also creates
62
67
  * the coin ATA), ~49,000 CU for a sell; the default leaves about 2x headroom (fee scheduler,
@@ -83,7 +88,11 @@ export const SWAP_ERROR_CODES = [
83
88
  'POOL_NOT_FOUND',
84
89
  'UNSUPPORTED_POOL',
85
90
  'POOL_GRADUATED',
91
+ /** The pool's config is not in the caller's `allowedConfigs` (e.g. another launchpad's coin). */
92
+ 'POOL_NOT_ALLOWED',
86
93
  'PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT',
94
+ /** The platform fee wallet is not FORGE_PLATFORM_FEE_WALLET, or not a plain system account. */
95
+ 'PLATFORM_FEE_WALLET_MISMATCH',
87
96
  'REFERRAL_ACCOUNT_INVALID',
88
97
  'INSUFFICIENT_LIQUIDITY',
89
98
  'QUOTE_FAILED',
@@ -419,8 +428,19 @@ function coinAtaSize(tokenProgram) {
419
428
  ? getAccountLen([ExtensionType.ImmutableOwner])
420
429
  : ACCOUNT_SIZE;
421
430
  }
422
- /** Throws unless the platform fee wallet exists and holds at least its rent-exempt minimum. */
423
- export function assertPlatformFeeWalletReady(wallet, account, rentExemptMinimum) {
431
+ /**
432
+ * Throws unless the platform fee wallet exists and holds at least its rent-exempt minimum. With
433
+ * `expectedWallet` (FORGE_PLATFORM_FEE_WALLET, as `buildBuyTx` / `buildSellTx` pass it), the
434
+ * wallet must be exactly that key and a plain system account: lamports sent to an account owned by
435
+ * another program could not be spent by FORGE (`PLATFORM_FEE_WALLET_MISMATCH`).
436
+ */
437
+ export function assertPlatformFeeWalletReady(wallet, account, rentExemptMinimum, expectedWallet) {
438
+ if (expectedWallet && !wallet.equals(expectedWallet)) {
439
+ throw new SwapError('PLATFORM_FEE_WALLET_MISMATCH', 'The platform fee must go to FORGE_PLATFORM_FEE_WALLET and nowhere else', { details: { platformFeeWallet: wallet.toBase58() } });
440
+ }
441
+ if (expectedWallet && account && !account.owner.equals(SystemProgram.programId)) {
442
+ throw new SwapError('PLATFORM_FEE_WALLET_MISMATCH', 'FORGE_PLATFORM_FEE_WALLET must be a system account (a wallet)', { details: { platformFeeWallet: wallet.toBase58(), owner: account.owner.toBase58() } });
443
+ }
424
444
  const lamports = account?.lamports ?? 0n;
425
445
  if (!account || lamports < rentExemptMinimum) {
426
446
  throw new SwapError('PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT', 'FORGE_PLATFORM_FEE_WALLET must exist and be rent-exempt, otherwise every swap fails on-chain', {
@@ -432,8 +452,12 @@ export function assertPlatformFeeWalletReady(wallet, account, rentExemptMinimum)
432
452
  });
433
453
  }
434
454
  }
435
- /** Throws unless the referral account is an initialized wrapped SOL token account (SPL Token). */
436
- export function assertReferralAccountReady(address, account) {
455
+ /**
456
+ * Throws unless the referral account is an initialized wrapped SOL token account (SPL Token).
457
+ * With `expectedOwner` (FORGE_MULTISIG_VAULT, when the environment has it), the token account must
458
+ * be held by that key: otherwise the referral share would be paid to someone else.
459
+ */
460
+ export function assertReferralAccountReady(address, account, expectedOwner) {
437
461
  const fail = (reason, cause) => new SwapError('REFERRAL_ACCOUNT_INVALID', `FORGE_METEORA_REFERRAL_ACCOUNT ${reason}`, {
438
462
  details: { referralTokenAccount: address.toBase58() },
439
463
  cause,
@@ -453,6 +477,27 @@ export function assertReferralAccountReady(address, account) {
453
477
  throw fail('is not a wrapped SOL account');
454
478
  if (!token.isInitialized || token.isFrozen)
455
479
  throw fail('is not usable (uninitialized or frozen)');
480
+ if (expectedOwner && !token.owner.equals(expectedOwner)) {
481
+ throw fail('is not held by FORGE_MULTISIG_VAULT');
482
+ }
483
+ }
484
+ function toConfigKeys(allowedConfigs) {
485
+ if (!Array.isArray(allowedConfigs))
486
+ throw invalid('allowedConfigs must be an array');
487
+ return allowedConfigs.map((config, i) => toPublicKey(`allowedConfigs[${i}]`, config));
488
+ }
489
+ /**
490
+ * Throws `POOL_NOT_ALLOWED` unless the pool's config is one of `allowedConfigs`. Without a list
491
+ * (`undefined`), any config is accepted; an empty list accepts none.
492
+ */
493
+ export function assertPoolConfigAllowed(pool, config, allowedConfigs) {
494
+ if (allowedConfigs === undefined)
495
+ return;
496
+ if (!toConfigKeys(allowedConfigs).some((allowed) => allowed.equals(config))) {
497
+ throw new SwapError('POOL_NOT_ALLOWED', 'This pool does not belong to this launchpad', {
498
+ details: { pool: pool.toBase58(), config: config.toBase58() },
499
+ });
500
+ }
456
501
  }
457
502
  function tokenBalance(address, account, tokenProgram) {
458
503
  if (!account || !account.owner.equals(tokenProgram))
@@ -523,13 +568,14 @@ export function compileSwapTransaction(args) {
523
568
  return { transaction, sizeBytes };
524
569
  }
525
570
  /**
526
- * Pure transaction builder: validates the context (pool on its curve, fee accounts ready, balances),
527
- * quotes, and assembles the instructions in the order documented at the top of this file.
571
+ * Pure transaction builder: validates the context (pool on its curve, fee accounts ready and, with
572
+ * `expected`, exactly FORGE's), quotes, and assembles the instructions in the order documented at
573
+ * the top of this file.
528
574
  */
529
- export function buildSwapTransaction(ctx, request) {
575
+ export function buildSwapTransaction(ctx, request, expected = {}) {
530
576
  const options = readBuildOptions(request);
531
- assertPlatformFeeWalletReady(ctx.platformFeeWallet, ctx.platformFeeWalletAccount, ctx.platformFeeWalletRentExemptMinimum);
532
- assertReferralAccountReady(ctx.referralTokenAccount, ctx.referralTokenAccountInfo);
577
+ assertPlatformFeeWalletReady(ctx.platformFeeWallet, ctx.platformFeeWalletAccount, ctx.platformFeeWalletRentExemptMinimum, expected.platformFeeWallet);
578
+ assertReferralAccountReady(ctx.referralTokenAccount, ctx.referralTokenAccountInfo, expected.referralOwner);
533
579
  const quote = computeSwapQuote(ctx, request);
534
580
  const side = request.side;
535
581
  const coinProgram = coinTokenProgram(ctx.virtualPool);
@@ -707,13 +753,20 @@ async function getCurrentPointRobust(connection, activationType, commitment) {
707
753
  // Only used for the quote (fee scheduler); the program uses its own clock.
708
754
  return new BN(blockTime ?? Math.floor(Date.now() / 1000));
709
755
  }
710
- /** Reads and decodes a pool and its config, plus the current point. */
711
- export async function fetchSwapPoolState(connection, poolAddress, commitment = DEFAULT_COMMITMENT) {
756
+ /**
757
+ * Reads and decodes a pool and its config, plus the current point. With
758
+ * `options.allowedConfigs`, a pool whose config is not in the list is refused (`POOL_NOT_ALLOWED`)
759
+ * before its config is read.
760
+ */
761
+ export async function fetchSwapPoolState(connection, poolAddress, commitment = DEFAULT_COMMITMENT, options = {}) {
712
762
  const pool = toPublicKey('pool', poolAddress);
763
+ if (options.allowedConfigs !== undefined)
764
+ toConfigKeys(options.allowedConfigs);
713
765
  const program = dbcProgram(connection);
714
766
  const poolInfo = await rpc('getAccountInfo(pool)', () => connection.getAccountInfo(pool, commitment));
715
767
  const virtualPool = decodeDbcAccount(program, 'virtualPool', pool, snapshot(poolInfo));
716
768
  const configAddress = virtualPool.poolState.config;
769
+ assertPoolConfigAllowed(pool, configAddress, options.allowedConfigs);
717
770
  const configInfo = await rpc('getAccountInfo(config)', () => connection.getAccountInfo(configAddress, commitment));
718
771
  const config = decodeDbcAccount(program, 'poolConfig', configAddress, snapshot(configInfo));
719
772
  const currentPoint = await getCurrentPointRobust(connection, config.activationType, commitment);
@@ -725,6 +778,8 @@ export async function fetchSwapContext(p) {
725
778
  const commitment = p.commitment ?? DEFAULT_COMMITMENT;
726
779
  const user = toPublicKey('user', p.user);
727
780
  const pool = toPublicKey('pool', p.pool);
781
+ if (p.allowedConfigs !== undefined)
782
+ toConfigKeys(p.allowedConfigs);
728
783
  const program = dbcProgram(connection);
729
784
  const [first, latest, slot] = await Promise.all([
730
785
  rpc('getMultipleAccountsInfo', () => connection.getMultipleAccountsInfo([pool, p.platformFeeWallet, p.referralTokenAccount, user], commitment)),
@@ -734,6 +789,7 @@ export async function fetchSwapContext(p) {
734
789
  const [poolInfo, platformInfo, referralInfo, userInfo] = first.map(snapshot);
735
790
  const virtualPool = decodeDbcAccount(program, 'virtualPool', pool, poolInfo ?? null);
736
791
  const configAddress = virtualPool.poolState.config;
792
+ assertPoolConfigAllowed(pool, configAddress, p.allowedConfigs);
737
793
  const coinProgram = coinTokenProgram(virtualPool);
738
794
  const userCoinAta = getAssociatedTokenAddressSync(virtualPool.poolState.baseMint, user, true, coinProgram);
739
795
  const userWrappedSolAta = getAssociatedTokenAddressSync(NATIVE_MINT, user, true, TOKEN_PROGRAM_ID);
@@ -775,12 +831,17 @@ export async function fetchSwapContext(p) {
775
831
  lastValidBlockHeight: latest.lastValidBlockHeight,
776
832
  };
777
833
  }
778
- /** FORGE fee accounts used by the swap, resolved through addresses.ts (never hard-coded). */
834
+ /**
835
+ * FORGE fee accounts used by the swap, resolved through addresses.ts (never hard-coded), and the
836
+ * multisig vault that must hold the referral account (`null` when the environment does not have
837
+ * `FORGE_MULTISIG_VAULT`: the owner check is then skipped).
838
+ */
779
839
  export function swapFeeAddresses(env) {
780
840
  const a = requireAddresses(['FORGE_PLATFORM_FEE_WALLET', 'FORGE_METEORA_REFERRAL_ACCOUNT'], env);
781
841
  return {
782
842
  platformFeeWallet: a.FORGE_PLATFORM_FEE_WALLET,
783
843
  referralTokenAccount: a.FORGE_METEORA_REFERRAL_ACCOUNT,
844
+ multisigVault: readOptionalAddress('FORGE_MULTISIG_VAULT', env),
784
845
  };
785
846
  }
786
847
  function slippageOf(p) {
@@ -791,7 +852,9 @@ async function buildSide(side, amountIn, p) {
791
852
  assertU64(side === 'buy' ? 'amountInLamports' : 'amountInTokens', amountIn, 1n);
792
853
  const slippage = readSlippage(slippageOf(p));
793
854
  const options = readBuildOptions(p);
794
- const { platformFeeWallet, referralTokenAccount } = swapFeeAddresses(p.env);
855
+ const { platformFeeWallet, referralTokenAccount, multisigVault } = swapFeeAddresses(p.env);
856
+ if (p.allowedConfigs !== undefined)
857
+ toConfigKeys(p.allowedConfigs);
795
858
  const ctx = await fetchSwapContext({
796
859
  connection: p.connection,
797
860
  pool: p.pool,
@@ -799,8 +862,9 @@ async function buildSide(side, amountIn, p) {
799
862
  platformFeeWallet,
800
863
  referralTokenAccount,
801
864
  ...(p.commitment ? { commitment: p.commitment } : {}),
865
+ ...(p.allowedConfigs !== undefined ? { allowedConfigs: p.allowedConfigs } : {}),
802
866
  });
803
- return buildSwapTransaction(ctx, { side, amountIn, slippage, ...options });
867
+ return buildSwapTransaction(ctx, { side, amountIn, slippage, ...options }, { platformFeeWallet, referralOwner: multisigVault });
804
868
  }
805
869
  /** Unsigned buy transaction (SOL -> coin) with the FORGE referral and the platform fee. */
806
870
  export async function buildBuyTx(params) {
@@ -819,18 +883,19 @@ export async function quoteSwap(params) {
819
883
  const slippage = params.slippageBps === undefined && params.minAmountOut === undefined
820
884
  ? { slippageBps: 0 }
821
885
  : readSlippage(slippageOf(params));
822
- const state = await fetchSwapPoolState(params.connection, params.pool, params.commitment);
886
+ const state = await fetchSwapPoolState(params.connection, params.pool, params.commitment, params.allowedConfigs !== undefined ? { allowedConfigs: params.allowedConfigs } : {});
823
887
  return computeSwapQuote(state, { side: params.side, amountIn: params.amountIn, slippage });
824
888
  }
825
889
  /**
826
- * Startup / pre-build check of the FORGE fee accounts: the platform fee wallet must be rent-exempt
827
- * and the referral account must be a wrapped SOL token account. Throws `SwapError`.
890
+ * Startup / pre-build check of the FORGE fee accounts: the platform fee wallet must be a
891
+ * rent-exempt system account and the referral account must be a wrapped SOL token account, held by
892
+ * `FORGE_MULTISIG_VAULT` when the environment has it. Throws `SwapError`.
828
893
  */
829
894
  export async function assertSwapFeeAccountsReady(params) {
830
895
  const commitment = params.commitment ?? DEFAULT_COMMITMENT;
831
- const { platformFeeWallet, referralTokenAccount } = swapFeeAddresses(params.env);
896
+ const { platformFeeWallet, referralTokenAccount, multisigVault } = swapFeeAddresses(params.env);
832
897
  const [platformInfo, referralInfo] = (await rpc('getMultipleAccountsInfo', () => params.connection.getMultipleAccountsInfo([platformFeeWallet, referralTokenAccount], commitment))).map(snapshot);
833
898
  const rentMinimum = BigInt(await rpc('getMinimumBalanceForRentExemption', () => params.connection.getMinimumBalanceForRentExemption(platformInfo?.data.length ?? 0, commitment)));
834
- assertPlatformFeeWalletReady(platformFeeWallet, platformInfo ?? null, rentMinimum);
835
- assertReferralAccountReady(referralTokenAccount, referralInfo ?? null);
899
+ assertPlatformFeeWalletReady(platformFeeWallet, platformInfo ?? null, rentMinimum, platformFeeWallet);
900
+ assertReferralAccountReady(referralTokenAccount, referralInfo ?? null, multisigVault);
836
901
  }
@@ -8,6 +8,11 @@
8
8
  * pool creation fee 0 or 0.001..100 SOL, ...), using the SDK constants and helpers;
9
9
  * - FORGE's stricter bounds (client trading fee 50..200 bps, pool creation fee 0 or 0.001..1 SOL,
10
10
  * migration threshold exactly 10 SOL, SOL quote, immutable metadata, ...);
11
+ * - FORGE's fixed settings, for every field that moves value: no migration fee, no locked
12
+ * vesting, the token supply of `FORGE_TOKEN_SETTINGS` (fixed, nothing minted later), FORGE's
13
+ * curve and start price (they decide the leftover sent to the leftover receiver), timestamp
14
+ * activation, and the anti-sniper schedule exactly `ANTI_SNIPER_FEE_SCHEDULE` (or a flat fee
15
+ * when `spec.antiSniper` is off);
11
16
  * - that the parameters match the spec they were built from;
12
17
  * - finally, the SDK's own `validateConfigParameters`, as a catch-all for curve-level rules.
13
18
  * Lamport amounts are compared as `bigint`.
@@ -15,7 +20,15 @@
15
20
  import { type ConfigParameters } from '@meteora-ag/dynamic-bonding-curve-sdk';
16
21
  import { PublicKey } from '@solana/web3.js';
17
22
  import type { LaunchpadSpec } from '@forge/shared';
18
- export type ViolationCode = 'INVALID_ADDRESS' | 'QUOTE_NOT_SOL' | 'FEE_CLAIMER_MISMATCH' | 'LEFTOVER_RECEIVER_MISMATCH' | 'FEE_MODE_NOT_ALLOWED' | 'FEE_OUT_OF_SDK_BOUNDS' | 'FEE_OUT_OF_CLIENT_BOUNDS' | 'FEE_MISMATCH' | 'FEE_SCHEDULER_INVALID' | 'FEE_SCHEDULER_TOO_LONG' | 'ANTI_SNIPER_MISMATCH' | 'DYNAMIC_FEE_NOT_ALLOWED' | 'COLLECT_FEE_MODE_NOT_ALLOWED' | 'MIGRATION_OPTION_NOT_ALLOWED' | 'MIGRATION_FEE_OPTION_INVALID' | 'MIGRATION_FEE_INVALID' | 'MIGRATED_POOL_FEE_OUT_OF_BOUNDS' | 'MIGRATED_POOL_FEE_INVALID' | 'MIGRATION_THRESHOLD_MISMATCH' | 'POOL_CREATION_FEE_OUT_OF_SDK_BOUNDS' | 'POOL_CREATION_FEE_OUT_OF_CLIENT_BOUNDS' | 'POOL_CREATION_FEE_MISMATCH' | 'CREATOR_TRADING_FEE_OUT_OF_BOUNDS' | 'CREATOR_TRADING_FEE_MISMATCH' | 'LP_PERCENTAGES_INVALID' | 'LIQUIDITY_VESTING_INVALID' | 'LOCKED_LIQUIDITY_TOO_LOW' | 'CREATOR_LOCKED_LIQUIDITY_REQUIRED' | 'TOKEN_UPDATE_AUTHORITY_NOT_IMMUTABLE' | 'TOKEN_TYPE_INVALID' | 'TOKEN_DECIMAL_INVALID' | 'ACTIVATION_TYPE_INVALID' | 'FIRST_SWAP_MIN_FEE_REQUIRED'
23
+ export type ViolationCode = 'INVALID_ADDRESS' | 'QUOTE_NOT_SOL' | 'FEE_CLAIMER_MISMATCH' | 'LEFTOVER_RECEIVER_MISMATCH' | 'FEE_MODE_NOT_ALLOWED' | 'FEE_OUT_OF_SDK_BOUNDS' | 'FEE_OUT_OF_CLIENT_BOUNDS' | 'FEE_MISMATCH' | 'FEE_SCHEDULER_INVALID' | 'FEE_SCHEDULER_TOO_LONG' | 'ANTI_SNIPER_MISMATCH' | 'DYNAMIC_FEE_NOT_ALLOWED' | 'COLLECT_FEE_MODE_NOT_ALLOWED' | 'MIGRATION_OPTION_NOT_ALLOWED' | 'MIGRATION_FEE_OPTION_INVALID' | 'MIGRATION_FEE_INVALID'
24
+ /** A migration fee (to the partner or the creator) is set: FORGE configs take none. */
25
+ | 'MIGRATION_FEE_NOT_ALLOWED' | 'MIGRATED_POOL_FEE_OUT_OF_BOUNDS' | 'MIGRATED_POOL_FEE_INVALID' | 'MIGRATION_THRESHOLD_MISMATCH' | 'POOL_CREATION_FEE_OUT_OF_SDK_BOUNDS' | 'POOL_CREATION_FEE_OUT_OF_CLIENT_BOUNDS' | 'POOL_CREATION_FEE_MISMATCH' | 'CREATOR_TRADING_FEE_OUT_OF_BOUNDS' | 'CREATOR_TRADING_FEE_MISMATCH' | 'LP_PERCENTAGES_INVALID' | 'LIQUIDITY_VESTING_INVALID' | 'LOCKED_LIQUIDITY_TOO_LOW' | 'CREATOR_LOCKED_LIQUIDITY_REQUIRED' | 'TOKEN_UPDATE_AUTHORITY_NOT_IMMUTABLE'
26
+ /** Locked vesting of base tokens is set: FORGE configs vest nothing. */
27
+ | 'LOCKED_VESTING_NOT_ALLOWED'
28
+ /** Token supply differs from `FORGE_TOKEN_SETTINGS` (1,000,000,000 tokens, 6 decimals, fixed). */
29
+ | 'TOKEN_SUPPLY_MISMATCH'
30
+ /** Start price or curve differs from FORGE's (they decide the swap / migration / leftover split). */
31
+ | 'CURVE_MISMATCH' | 'TOKEN_TYPE_INVALID' | 'TOKEN_DECIMAL_INVALID' | 'ACTIVATION_TYPE_INVALID' | 'FIRST_SWAP_MIN_FEE_REQUIRED'
19
32
  /** Coin name + symbol + metadata URI exceed the launch transaction budget. */
20
33
  | 'LAUNCH_METADATA_TOO_LONG' | 'SPEC_INVALID' | 'SDK_REJECTED';
21
34
  export interface Violation {
package/dist/validate.js CHANGED
@@ -8,6 +8,11 @@
8
8
  * pool creation fee 0 or 0.001..100 SOL, ...), using the SDK constants and helpers;
9
9
  * - FORGE's stricter bounds (client trading fee 50..200 bps, pool creation fee 0 or 0.001..1 SOL,
10
10
  * migration threshold exactly 10 SOL, SOL quote, immutable metadata, ...);
11
+ * - FORGE's fixed settings, for every field that moves value: no migration fee, no locked
12
+ * vesting, the token supply of `FORGE_TOKEN_SETTINGS` (fixed, nothing minted later), FORGE's
13
+ * curve and start price (they decide the leftover sent to the leftover receiver), timestamp
14
+ * activation, and the anti-sniper schedule exactly `ANTI_SNIPER_FEE_SCHEDULE` (or a flat fee
15
+ * when `spec.antiSniper` is off);
11
16
  * - that the parameters match the spec they were built from;
12
17
  * - finally, the SDK's own `validateConfigParameters`, as a catch-all for curve-level rules.
13
18
  * Lamport amounts are compared as `bigint`.
@@ -16,6 +21,7 @@ import { ActivationType, BaseFeeMode, CollectFeeMode, MigrationFeeOption, Migrat
16
21
  import { PublicKey } from '@solana/web3.js';
17
22
  import BN from 'bn.js';
18
23
  import { CLIENT_BOUNDS, CLIENT_POOL_CREATION_FEE_LAMPORTS, FEE_DENOMINATOR, FEES, MAX_ANTI_SNIPER_DURATION_SECONDS, MAX_ANTI_SNIPER_DURATION_SLOTS, MAX_ANTI_SNIPER_STARTING_FEE_BPS, MAX_BASIS_POINT, MAX_FEE_BPS, MAX_FEE_NUMERATOR, MAX_FIRST_BUY_SOL, MAX_MIGRATED_POOL_FEE_BPS, MAX_POOL_CREATION_FEE, MIGRATION_THRESHOLD_LAMPORTS, MIN_FEE_BPS, MIN_FEE_NUMERATOR, MIN_LOCKED_LIQUIDITY_BPS, MIN_MIGRATED_POOL_FEE_BPS, MIN_POOL_CREATION_FEE, SECONDS_PER_DAY, SOL_QUOTE_MINT, solToLamports, } from './constants.js';
24
+ import { ANTI_SNIPER_FEE_SCHEDULE, FORGE_ACTIVATION_TYPE, FORGE_TOKEN_SETTINGS, FORGE_TOKEN_SUPPLY_RAW, forgeBaseFee, forgeReferenceCurve, } from './settings.js';
19
25
  /** Thrown by `assertValid` when a result has violations. */
20
26
  export class ValidationError extends Error {
21
27
  violations;
@@ -95,6 +101,15 @@ const FEE_NUMERATOR_PER_BPS = BigInt(FEE_DENOMINATOR / MAX_BASIS_POINT);
95
101
  function bpsToNumerator(bps) {
96
102
  return BigInt(bpsToFeeNumerator(bps).toString());
97
103
  }
104
+ /** FORGE's base fee for this fee and anti-sniper flag, or null when the SDK refuses the fee. */
105
+ function expectedBaseFee(tradingFeeBps, antiSniper) {
106
+ try {
107
+ return forgeBaseFee(tradingFeeBps, antiSniper);
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
98
113
  /** Fee numerator to bps, rounded to the nearest bps (for messages and the bounds check). */
99
114
  function numeratorToRoundedBps(numerator) {
100
115
  return Number((numerator + FEE_NUMERATOR_PER_BPS / 2n) / FEE_NUMERATOR_PER_BPS);
@@ -212,6 +227,7 @@ function validateConfig(config, spec, expected) {
212
227
  collectTradingFee(config, spec, c);
213
228
  collectModes(config, c);
214
229
  collectMigration(config, c);
230
+ collectSupply(config, c);
215
231
  collectPoolCreationFee(config, spec, expected, c);
216
232
  collectCreatorTradingFee(config, expected, c);
217
233
  collectLiquidity(config, expected, c);
@@ -343,6 +359,26 @@ function collectTradingFee(config, spec, c) {
343
359
  else if (spec?.antiSniper === false && !schedulerOff) {
344
360
  c.add(field, 'ANTI_SNIPER_MISMATCH', 'spec.antiSniper is off: the fee scheduler must be disabled');
345
361
  }
362
+ // FORGE's schedule exactly, not just one within the bounds: a single long period at 99% (or an
363
+ // exponential curve) would keep traders paying the starting fee far longer than announced.
364
+ const reported = c.violations.some((v) => v.field === field && (v.code === 'ANTI_SNIPER_MISMATCH' || v.code === 'FEE_MISMATCH'));
365
+ if (!reported &&
366
+ typeof spec?.antiSniper === 'boolean' &&
367
+ isIntInRange(spec.tradingFeeBps, MIN_FEE_BPS, MAX_FEE_BPS)) {
368
+ const forge = expectedBaseFee(spec.tradingFeeBps, spec.antiSniper);
369
+ const same = forge !== null &&
370
+ toBigInt(forge.cliffFeeNumerator) === cliff &&
371
+ forge.firstFactor === numberOfPeriod &&
372
+ toBigInt(forge.secondFactor) === periodFrequency &&
373
+ toBigInt(forge.thirdFactor) === reductionFactor &&
374
+ forge.baseFeeMode === mode;
375
+ if (!same) {
376
+ const schedule = ANTI_SNIPER_FEE_SCHEDULE;
377
+ c.add(field, spec.antiSniper ? 'ANTI_SNIPER_MISMATCH' : 'FEE_MISMATCH', spec.antiSniper
378
+ ? `the anti-sniper schedule must be exactly ANTI_SNIPER_FEE_SCHEDULE: linear from ${schedule.startingFeeBps} bps to spec.tradingFeeBps in ${schedule.numberOfPeriod} periods over ${schedule.totalDurationSeconds} s`
379
+ : 'spec.antiSniper is off: the base fee must be a flat spec.tradingFeeBps in FeeSchedulerLinear mode');
380
+ }
381
+ }
346
382
  if (config.poolFees.dynamicFee !== null && config.poolFees.dynamicFee !== undefined) {
347
383
  c.add('poolFees.dynamicFee', 'DYNAMIC_FEE_NOT_ALLOWED', 'dynamic fee must be disabled (it would raise the fee above the client bounds)');
348
384
  }
@@ -351,14 +387,53 @@ function collectModes(config, c) {
351
387
  if (config.collectFeeMode !== CollectFeeMode.QuoteToken) {
352
388
  c.add('collectFeeMode', 'COLLECT_FEE_MODE_NOT_ALLOWED', 'collectFeeMode must be QuoteToken (fees and the referral are paid in SOL)');
353
389
  }
354
- if (!enumValues(ActivationType).includes(config.activationType)) {
355
- c.add('activationType', 'ACTIVATION_TYPE_INVALID', 'activationType must be Slot or Timestamp');
390
+ // The anti-sniper periods are counted in seconds: a slot-based config would run them in slots.
391
+ if (!enumValues(ActivationType).includes(config.activationType) ||
392
+ config.activationType !== FORGE_ACTIVATION_TYPE) {
393
+ c.add('activationType', 'ACTIVATION_TYPE_INVALID', 'activationType must be Timestamp (FORGE_ACTIVATION_TYPE)');
394
+ }
395
+ if (!enumValues(TokenType).includes(config.tokenType) ||
396
+ config.tokenType !== FORGE_TOKEN_SETTINGS.tokenType) {
397
+ c.add('tokenType', 'TOKEN_TYPE_INVALID', 'tokenType must be SPLToken (FORGE_TOKEN_SETTINGS)');
356
398
  }
357
- if (!enumValues(TokenType).includes(config.tokenType)) {
358
- c.add('tokenType', 'TOKEN_TYPE_INVALID', 'tokenType must be SPLToken or Token2022');
399
+ if (!enumValues(TokenDecimal).includes(config.tokenDecimal) ||
400
+ config.tokenDecimal !== FORGE_TOKEN_SETTINGS.tokenBaseDecimal) {
401
+ c.add('tokenDecimal', 'TOKEN_DECIMAL_INVALID', `tokenDecimal must be ${FORGE_TOKEN_SETTINGS.tokenBaseDecimal} (FORGE_TOKEN_SETTINGS)`);
359
402
  }
360
- if (!enumValues(TokenDecimal).includes(config.tokenDecimal)) {
361
- c.add('tokenDecimal', 'TOKEN_DECIMAL_INVALID', 'tokenDecimal must be between 6 and 9');
403
+ }
404
+ /**
405
+ * Supply side (every field decides where tokens go): no locked vesting, FORGE's fixed token
406
+ * supply, and FORGE's curve and start price (with a fixed supply they decide how much is swapped,
407
+ * migrated and left over to the leftover receiver).
408
+ */
409
+ function collectSupply(config, c) {
410
+ const vesting = config.lockedVesting;
411
+ const vestingFields = vesting
412
+ ? [
413
+ vesting.amountPerPeriod,
414
+ vesting.cliffDurationFromMigrationTime,
415
+ vesting.frequency,
416
+ vesting.numberOfPeriod,
417
+ vesting.cliffUnlockAmount,
418
+ ]
419
+ : [];
420
+ if (!vesting || vestingFields.some((value) => toBigInt(value) !== 0n)) {
421
+ c.add('lockedVesting', 'LOCKED_VESTING_NOT_ALLOWED', 'lockedVesting must be all zero (FORGE configs vest no base token)');
422
+ }
423
+ const supply = config.tokenSupply;
424
+ if (!supply ||
425
+ toBigInt(supply.preMigrationTokenSupply) !== FORGE_TOKEN_SUPPLY_RAW ||
426
+ toBigInt(supply.postMigrationTokenSupply) !== FORGE_TOKEN_SUPPLY_RAW) {
427
+ c.add('tokenSupply', 'TOKEN_SUPPLY_MISMATCH', `tokenSupply must be fixed at ${FORGE_TOKEN_SUPPLY_RAW} raw units before and after migration (FORGE_TOKEN_SETTINGS)`);
428
+ }
429
+ const reference = forgeReferenceCurve();
430
+ const curve = Array.isArray(config.curve) ? config.curve : [];
431
+ const sameCurve = toBigInt(config.sqrtStartPrice) === toBigInt(reference.sqrtStartPrice) &&
432
+ curve.length === reference.curve.length &&
433
+ reference.curve.every((point, i) => toBigInt(curve[i]?.sqrtPrice) === toBigInt(point.sqrtPrice) &&
434
+ toBigInt(curve[i]?.liquidity) === toBigInt(point.liquidity));
435
+ if (!sameCurve) {
436
+ c.add('curve', 'CURVE_MISMATCH', "sqrtStartPrice and curve must be FORGE's (FORGE_TOKEN_SETTINGS, FORGE_MIGRATION_SETTINGS, 10 SOL threshold)");
362
437
  }
363
438
  }
364
439
  function collectMigration(config, c) {
@@ -379,6 +454,11 @@ function collectMigration(config, c) {
379
454
  if (feeReason !== null) {
380
455
  c.add('migrationFee', 'MIGRATION_FEE_INVALID', `migrationFee: ${feeReason}`);
381
456
  }
457
+ else if (config.migrationFee.feePercentage !== 0 ||
458
+ config.migrationFee.creatorFeePercentage !== 0) {
459
+ // The SDK allows up to 99% / 100% of the migrated quote: on a FORGE config it is always 0.
460
+ c.add('migrationFee', 'MIGRATION_FEE_NOT_ALLOWED', 'migrationFee.feePercentage and migrationFee.creatorFeePercentage must be 0');
461
+ }
382
462
  const pool = config.migratedPoolFee;
383
463
  if (!pool) {
384
464
  c.add('migratedPoolFee', 'MIGRATED_POOL_FEE_INVALID', 'migratedPoolFee is required');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgedevv/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "FORGE transaction core: Meteora Dynamic Bonding Curve configs, pools, swaps, claims and on-chain checks. Published on npm as @forgedevv/core.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -47,7 +47,8 @@
47
47
  "registry": "https://registry.npmjs.org/"
48
48
  },
49
49
  "dependencies": {
50
- "@forge/shared": "npm:@forgedevv/shared@0.1.0",
50
+ "@forge/shared": "npm:@forgedevv/shared@0.2.0",
51
+ "@meteora-ag/cp-amm-sdk": "1.5.1",
51
52
  "@meteora-ag/dynamic-bonding-curve-sdk": "1.5.13",
52
53
  "@solana/spl-token": "0.4.15",
53
54
  "@solana/web3.js": "1.99.0",