@forgedevv/core 0.1.0 → 0.3.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/CHANGELOG.md +227 -4
- package/README.md +4 -3
- package/dist/addresses.d.ts +73 -7
- package/dist/addresses.js +96 -5
- package/dist/claims.d.ts +43 -6
- package/dist/claims.js +75 -16
- package/dist/config.d.ts +48 -37
- package/dist/config.js +282 -52
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +20 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +15 -4
- package/dist/jupiter-referral.d.ts +334 -0
- package/dist/jupiter-referral.js +666 -0
- package/dist/launch-metadata.d.ts +5 -1
- package/dist/launch-metadata.js +2 -0
- package/dist/meteora-referral.d.ts +119 -0
- package/dist/meteora-referral.js +188 -0
- package/dist/payments.d.ts +17 -0
- package/dist/payments.js +20 -2
- package/dist/pool.d.ts +108 -17
- package/dist/pool.js +237 -60
- package/dist/position-claims.d.ts +215 -0
- package/dist/position-claims.js +457 -0
- package/dist/priority-fee.d.ts +202 -0
- package/dist/priority-fee.js +322 -0
- package/dist/reads.d.ts +20 -0
- package/dist/reads.js +39 -2
- package/dist/settings.d.ts +73 -0
- package/dist/settings.js +119 -0
- package/dist/swap.d.ts +95 -23
- package/dist/swap.js +144 -30
- package/dist/tx.d.ts +2 -0
- package/dist/tx.js +4 -2
- package/dist/validate.d.ts +14 -1
- package/dist/validate.js +86 -6
- package/package.json +3 -2
package/dist/swap.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* One transaction contains, in this order:
|
|
6
6
|
*
|
|
7
7
|
* 1. compute budget: `setComputeUnitLimit`, then `setComputeUnitPrice` (only when a priority fee
|
|
8
|
-
* is given
|
|
8
|
+
* is given: a fixed `priorityFeeMicroLamports`, or a `priority` setting whose price is read
|
|
9
|
+
* from the recent fees of the swap's writable accounts and bounded, see priority-fee.ts);
|
|
9
10
|
* 2. token accounts: the user's wrapped SOL ATA is always created (idempotent) and, on a buy, the
|
|
10
11
|
* user's coin ATA when it does not exist yet;
|
|
11
12
|
* 3. buy only: SOL transfer of `amountInLamports` into the wrapped SOL ATA + `SyncNative` (wrap);
|
|
@@ -27,19 +28,26 @@
|
|
|
27
28
|
* Refusals (typed `SwapError`, never a silent fallback):
|
|
28
29
|
* - the pool is migrated or its curve is complete (`POOL_GRADUATED`): the template trades through
|
|
29
30
|
* Jupiter in that case;
|
|
31
|
+
* - the pool's config is not in the caller's `allowedConfigs` (`POOL_NOT_ALLOWED`): a launchpad
|
|
32
|
+
* site only trades its own coins;
|
|
30
33
|
* - the platform fee wallet does not exist or is not rent-exempt
|
|
31
34
|
* (`PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT`): the whole swap would fail on-chain (devnet finding);
|
|
32
|
-
* - the
|
|
35
|
+
* - the platform fee wallet is not exactly `FORGE_PLATFORM_FEE_WALLET`, or not a system account,
|
|
36
|
+
* or `FORGE_PLATFORM_FEE_WALLET` is not the revenue wallet when the environment has
|
|
37
|
+
* `FORGE_REVENUE_WALLET` (`PLATFORM_FEE_WALLET_MISMATCH`);
|
|
38
|
+
* - the referral account is not an initialized wrapped SOL token account, or is not held by
|
|
39
|
+
* `FORGE_REVENUE_WALLET` when the environment has it (`REFERRAL_ACCOUNT_INVALID`);
|
|
33
40
|
* - the pool is not a plain SOL-quoted DBC pool (`UNSUPPORTED_POOL`; transfer-hook pools excluded);
|
|
34
41
|
* - the serialized transaction exceeds 1,232 bytes (`TRANSACTION_TOO_LARGE`).
|
|
35
42
|
*
|
|
36
43
|
* Transaction size: measured on devnet (scripts/devnet-tests/reports/core-swap.md) at 819 bytes for a
|
|
37
44
|
* buy and 797 bytes for a sell with this exact layout, far below the 1,232-byte limit (a buy that
|
|
38
|
-
* also creates the coin ATA adds 10 bytes
|
|
39
|
-
*
|
|
40
|
-
* `AddressLookupTableAccount`s, e.g. one holding the
|
|
41
|
-
*
|
|
42
|
-
* is then compiled against those tables and checked
|
|
45
|
+
* also creates the coin ATA adds 10 bytes, a priority fee 12 bytes for `setComputeUnitPrice`). If
|
|
46
|
+
* a future layout does not fit, the builders throw `TRANSACTION_TOO_LARGE` unless the caller
|
|
47
|
+
* passes `addressLookupTables` (already fetched `AddressLookupTableAccount`s, e.g. one holding the
|
|
48
|
+
* DBC program, the pool authority, the event authority, the token programs, the wrapped SOL mint
|
|
49
|
+
* and the two FORGE fee accounts); the message is then compiled against those tables and checked
|
|
50
|
+
* again.
|
|
43
51
|
*
|
|
44
52
|
* Layering: `fetchSwapContext` does all the network reads (with retries); everything else is pure
|
|
45
53
|
* and unit-tested without network (`buildSwapTransaction`, `computeSwapQuote`, fee math).
|
|
@@ -48,15 +56,16 @@ import { ComputeBudgetProgram, PACKET_DATA_SIZE, PublicKey, SYSVAR_INSTRUCTIONS_
|
|
|
48
56
|
import { ACCOUNT_SIZE, ExtensionType, NATIVE_MINT, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, createCloseAccountInstruction, createSyncNativeInstruction, getAccountLen, getAssociatedTokenAddressSync, unpackAccount, } from '@solana/spl-token';
|
|
49
57
|
import BN from 'bn.js';
|
|
50
58
|
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';
|
|
59
|
+
import { readOptionalAddress, requireAddresses } from './addresses.js';
|
|
52
60
|
import { FEES } from './constants.js';
|
|
61
|
+
import { estimatePriorityFee, fixedPriorityFee, resolvePriorityFeePolicy, } from './priority-fee.js';
|
|
53
62
|
// ---------------------------------------------------------------------------------------------
|
|
54
63
|
// Constants
|
|
55
64
|
// ---------------------------------------------------------------------------------------------
|
|
56
65
|
/** Platform fee on swaps built by @forge/core, in bps (`FEES.platformFeeBps` from @forge/shared). */
|
|
57
66
|
export const PLATFORM_FEE_BPS = FEES.platformFeeBps;
|
|
58
|
-
/** Highest slippage tolerance accepted by the builders (
|
|
59
|
-
export const MAX_SWAP_SLIPPAGE_BPS =
|
|
67
|
+
/** Highest slippage tolerance accepted by the builders (25%), whether given in bps or as a minimum. */
|
|
68
|
+
export const MAX_SWAP_SLIPPAGE_BPS = 2_500;
|
|
60
69
|
/**
|
|
61
70
|
* Default compute unit limit. Measured on devnet: ~51,000 CU for a buy (~66,500 when it also creates
|
|
62
71
|
* the coin ATA), ~49,000 CU for a sell; the default leaves about 2x headroom (fee scheduler,
|
|
@@ -83,7 +92,11 @@ export const SWAP_ERROR_CODES = [
|
|
|
83
92
|
'POOL_NOT_FOUND',
|
|
84
93
|
'UNSUPPORTED_POOL',
|
|
85
94
|
'POOL_GRADUATED',
|
|
95
|
+
/** The pool's config is not in the caller's `allowedConfigs` (e.g. another launchpad's coin). */
|
|
96
|
+
'POOL_NOT_ALLOWED',
|
|
86
97
|
'PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT',
|
|
98
|
+
/** The platform fee wallet is not FORGE_PLATFORM_FEE_WALLET, or not a plain system account. */
|
|
99
|
+
'PLATFORM_FEE_WALLET_MISMATCH',
|
|
87
100
|
'REFERRAL_ACCOUNT_INVALID',
|
|
88
101
|
'INSUFFICIENT_LIQUIDITY',
|
|
89
102
|
'QUOTE_FAILED',
|
|
@@ -419,8 +432,19 @@ function coinAtaSize(tokenProgram) {
|
|
|
419
432
|
? getAccountLen([ExtensionType.ImmutableOwner])
|
|
420
433
|
: ACCOUNT_SIZE;
|
|
421
434
|
}
|
|
422
|
-
/**
|
|
423
|
-
|
|
435
|
+
/**
|
|
436
|
+
* Throws unless the platform fee wallet exists and holds at least its rent-exempt minimum. With
|
|
437
|
+
* `expectedWallet` (FORGE_PLATFORM_FEE_WALLET, as `buildBuyTx` / `buildSellTx` pass it), the
|
|
438
|
+
* wallet must be exactly that key and a plain system account: lamports sent to an account owned by
|
|
439
|
+
* another program could not be spent by FORGE (`PLATFORM_FEE_WALLET_MISMATCH`).
|
|
440
|
+
*/
|
|
441
|
+
export function assertPlatformFeeWalletReady(wallet, account, rentExemptMinimum, expectedWallet) {
|
|
442
|
+
if (expectedWallet && !wallet.equals(expectedWallet)) {
|
|
443
|
+
throw new SwapError('PLATFORM_FEE_WALLET_MISMATCH', 'The platform fee must go to FORGE_PLATFORM_FEE_WALLET and nowhere else', { details: { platformFeeWallet: wallet.toBase58() } });
|
|
444
|
+
}
|
|
445
|
+
if (expectedWallet && account && !account.owner.equals(SystemProgram.programId)) {
|
|
446
|
+
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() } });
|
|
447
|
+
}
|
|
424
448
|
const lamports = account?.lamports ?? 0n;
|
|
425
449
|
if (!account || lamports < rentExemptMinimum) {
|
|
426
450
|
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 +456,12 @@ export function assertPlatformFeeWalletReady(wallet, account, rentExemptMinimum)
|
|
|
432
456
|
});
|
|
433
457
|
}
|
|
434
458
|
}
|
|
435
|
-
/**
|
|
436
|
-
|
|
459
|
+
/**
|
|
460
|
+
* Throws unless the referral account is an initialized wrapped SOL token account (SPL Token).
|
|
461
|
+
* With `expectedOwner` (FORGE_REVENUE_WALLET, when the environment has it), the token account must
|
|
462
|
+
* be held by that key: otherwise the referral share would be paid to someone else.
|
|
463
|
+
*/
|
|
464
|
+
export function assertReferralAccountReady(address, account, expectedOwner) {
|
|
437
465
|
const fail = (reason, cause) => new SwapError('REFERRAL_ACCOUNT_INVALID', `FORGE_METEORA_REFERRAL_ACCOUNT ${reason}`, {
|
|
438
466
|
details: { referralTokenAccount: address.toBase58() },
|
|
439
467
|
cause,
|
|
@@ -453,6 +481,27 @@ export function assertReferralAccountReady(address, account) {
|
|
|
453
481
|
throw fail('is not a wrapped SOL account');
|
|
454
482
|
if (!token.isInitialized || token.isFrozen)
|
|
455
483
|
throw fail('is not usable (uninitialized or frozen)');
|
|
484
|
+
if (expectedOwner && !token.owner.equals(expectedOwner)) {
|
|
485
|
+
throw fail('is not held by FORGE_REVENUE_WALLET');
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function toConfigKeys(allowedConfigs) {
|
|
489
|
+
if (!Array.isArray(allowedConfigs))
|
|
490
|
+
throw invalid('allowedConfigs must be an array');
|
|
491
|
+
return allowedConfigs.map((config, i) => toPublicKey(`allowedConfigs[${i}]`, config));
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Throws `POOL_NOT_ALLOWED` unless the pool's config is one of `allowedConfigs`. Without a list
|
|
495
|
+
* (`undefined`), any config is accepted; an empty list accepts none.
|
|
496
|
+
*/
|
|
497
|
+
export function assertPoolConfigAllowed(pool, config, allowedConfigs) {
|
|
498
|
+
if (allowedConfigs === undefined)
|
|
499
|
+
return;
|
|
500
|
+
if (!toConfigKeys(allowedConfigs).some((allowed) => allowed.equals(config))) {
|
|
501
|
+
throw new SwapError('POOL_NOT_ALLOWED', 'This pool does not belong to this launchpad', {
|
|
502
|
+
details: { pool: pool.toBase58(), config: config.toBase58() },
|
|
503
|
+
});
|
|
504
|
+
}
|
|
456
505
|
}
|
|
457
506
|
function tokenBalance(address, account, tokenProgram) {
|
|
458
507
|
if (!account || !account.owner.equals(tokenProgram))
|
|
@@ -484,6 +533,21 @@ function readBuildOptions(o) {
|
|
|
484
533
|
throw invalid('addressLookupTables must be an array');
|
|
485
534
|
return { computeUnitLimit, priorityFeeMicroLamports, addressLookupTables };
|
|
486
535
|
}
|
|
536
|
+
/** Checks a `priority` setting (INVALID_PARAMS), and that no fixed price is given with it. */
|
|
537
|
+
function readPrioritySetting(o) {
|
|
538
|
+
if (o.priority === undefined)
|
|
539
|
+
return undefined;
|
|
540
|
+
if (o.priorityFeeMicroLamports !== undefined) {
|
|
541
|
+
throw invalid('Pass either priority or priorityFeeMicroLamports, not both');
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
resolvePriorityFeePolicy(o.priority);
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
throw invalid(`priority: ${error instanceof Error ? error.message : String(error)}`);
|
|
548
|
+
}
|
|
549
|
+
return o.priority;
|
|
550
|
+
}
|
|
487
551
|
/**
|
|
488
552
|
* Compiles the instructions into an unsigned v0 transaction paid by `payer` and checks its
|
|
489
553
|
* serialized size (signature placeholders included) against 1,232 bytes.
|
|
@@ -523,13 +587,18 @@ export function compileSwapTransaction(args) {
|
|
|
523
587
|
return { transaction, sizeBytes };
|
|
524
588
|
}
|
|
525
589
|
/**
|
|
526
|
-
* Pure transaction builder: validates the context (pool on its curve, fee accounts ready,
|
|
527
|
-
* quotes, and assembles the instructions in the order documented at
|
|
590
|
+
* Pure transaction builder: validates the context (pool on its curve, fee accounts ready and, with
|
|
591
|
+
* `expected`, exactly FORGE's), quotes, and assembles the instructions in the order documented at
|
|
592
|
+
* the top of this file.
|
|
528
593
|
*/
|
|
529
|
-
export function buildSwapTransaction(ctx, request) {
|
|
594
|
+
export function buildSwapTransaction(ctx, request, expected = {}) {
|
|
595
|
+
if (request.priority !== undefined) {
|
|
596
|
+
// The price of a `priority` setting is read from the cluster: buildBuyTx / buildSellTx.
|
|
597
|
+
throw invalid('buildSwapTransaction takes a fixed priorityFeeMicroLamports, not priority');
|
|
598
|
+
}
|
|
530
599
|
const options = readBuildOptions(request);
|
|
531
|
-
assertPlatformFeeWalletReady(ctx.platformFeeWallet, ctx.platformFeeWalletAccount, ctx.platformFeeWalletRentExemptMinimum);
|
|
532
|
-
assertReferralAccountReady(ctx.referralTokenAccount, ctx.referralTokenAccountInfo);
|
|
600
|
+
assertPlatformFeeWalletReady(ctx.platformFeeWallet, ctx.platformFeeWalletAccount, ctx.platformFeeWalletRentExemptMinimum, expected.platformFeeWallet);
|
|
601
|
+
assertReferralAccountReady(ctx.referralTokenAccount, ctx.referralTokenAccountInfo, expected.referralOwner);
|
|
533
602
|
const quote = computeSwapQuote(ctx, request);
|
|
534
603
|
const side = request.side;
|
|
535
604
|
const coinProgram = coinTokenProgram(ctx.virtualPool);
|
|
@@ -623,6 +692,7 @@ export function buildSwapTransaction(ctx, request) {
|
|
|
623
692
|
sizeBytes,
|
|
624
693
|
computeUnitLimit: options.computeUnitLimit,
|
|
625
694
|
priorityFeeMicroLamports: options.priorityFeeMicroLamports,
|
|
695
|
+
priorityFee: fixedPriorityFee(options.computeUnitLimit, options.priorityFeeMicroLamports),
|
|
626
696
|
estimatedNetworkFeeLamports,
|
|
627
697
|
newCoinAccountRentLamports,
|
|
628
698
|
requiredLamports,
|
|
@@ -707,13 +777,20 @@ async function getCurrentPointRobust(connection, activationType, commitment) {
|
|
|
707
777
|
// Only used for the quote (fee scheduler); the program uses its own clock.
|
|
708
778
|
return new BN(blockTime ?? Math.floor(Date.now() / 1000));
|
|
709
779
|
}
|
|
710
|
-
/**
|
|
711
|
-
|
|
780
|
+
/**
|
|
781
|
+
* Reads and decodes a pool and its config, plus the current point. With
|
|
782
|
+
* `options.allowedConfigs`, a pool whose config is not in the list is refused (`POOL_NOT_ALLOWED`)
|
|
783
|
+
* before its config is read.
|
|
784
|
+
*/
|
|
785
|
+
export async function fetchSwapPoolState(connection, poolAddress, commitment = DEFAULT_COMMITMENT, options = {}) {
|
|
712
786
|
const pool = toPublicKey('pool', poolAddress);
|
|
787
|
+
if (options.allowedConfigs !== undefined)
|
|
788
|
+
toConfigKeys(options.allowedConfigs);
|
|
713
789
|
const program = dbcProgram(connection);
|
|
714
790
|
const poolInfo = await rpc('getAccountInfo(pool)', () => connection.getAccountInfo(pool, commitment));
|
|
715
791
|
const virtualPool = decodeDbcAccount(program, 'virtualPool', pool, snapshot(poolInfo));
|
|
716
792
|
const configAddress = virtualPool.poolState.config;
|
|
793
|
+
assertPoolConfigAllowed(pool, configAddress, options.allowedConfigs);
|
|
717
794
|
const configInfo = await rpc('getAccountInfo(config)', () => connection.getAccountInfo(configAddress, commitment));
|
|
718
795
|
const config = decodeDbcAccount(program, 'poolConfig', configAddress, snapshot(configInfo));
|
|
719
796
|
const currentPoint = await getCurrentPointRobust(connection, config.activationType, commitment);
|
|
@@ -725,6 +802,8 @@ export async function fetchSwapContext(p) {
|
|
|
725
802
|
const commitment = p.commitment ?? DEFAULT_COMMITMENT;
|
|
726
803
|
const user = toPublicKey('user', p.user);
|
|
727
804
|
const pool = toPublicKey('pool', p.pool);
|
|
805
|
+
if (p.allowedConfigs !== undefined)
|
|
806
|
+
toConfigKeys(p.allowedConfigs);
|
|
728
807
|
const program = dbcProgram(connection);
|
|
729
808
|
const [first, latest, slot] = await Promise.all([
|
|
730
809
|
rpc('getMultipleAccountsInfo', () => connection.getMultipleAccountsInfo([pool, p.platformFeeWallet, p.referralTokenAccount, user], commitment)),
|
|
@@ -734,6 +813,7 @@ export async function fetchSwapContext(p) {
|
|
|
734
813
|
const [poolInfo, platformInfo, referralInfo, userInfo] = first.map(snapshot);
|
|
735
814
|
const virtualPool = decodeDbcAccount(program, 'virtualPool', pool, poolInfo ?? null);
|
|
736
815
|
const configAddress = virtualPool.poolState.config;
|
|
816
|
+
assertPoolConfigAllowed(pool, configAddress, p.allowedConfigs);
|
|
737
817
|
const coinProgram = coinTokenProgram(virtualPool);
|
|
738
818
|
const userCoinAta = getAssociatedTokenAddressSync(virtualPool.poolState.baseMint, user, true, coinProgram);
|
|
739
819
|
const userWrappedSolAta = getAssociatedTokenAddressSync(NATIVE_MINT, user, true, TOKEN_PROGRAM_ID);
|
|
@@ -775,12 +855,23 @@ export async function fetchSwapContext(p) {
|
|
|
775
855
|
lastValidBlockHeight: latest.lastValidBlockHeight,
|
|
776
856
|
};
|
|
777
857
|
}
|
|
778
|
-
/**
|
|
858
|
+
/**
|
|
859
|
+
* FORGE fee accounts used by the swap, resolved through addresses.ts (never hard-coded), and the
|
|
860
|
+
* revenue wallet that must hold the referral account (`null` when the environment does not have
|
|
861
|
+
* `FORGE_REVENUE_WALLET`: the owner check is then skipped). When the environment has it, the
|
|
862
|
+
* platform fee wallet must be that same wallet (D34: the platform fee is revenue too), otherwise
|
|
863
|
+
* `PLATFORM_FEE_WALLET_MISMATCH`.
|
|
864
|
+
*/
|
|
779
865
|
export function swapFeeAddresses(env) {
|
|
780
866
|
const a = requireAddresses(['FORGE_PLATFORM_FEE_WALLET', 'FORGE_METEORA_REFERRAL_ACCOUNT'], env);
|
|
867
|
+
const revenueWallet = readOptionalAddress('FORGE_REVENUE_WALLET', env);
|
|
868
|
+
if (revenueWallet && !a.FORGE_PLATFORM_FEE_WALLET.equals(revenueWallet)) {
|
|
869
|
+
throw new SwapError('PLATFORM_FEE_WALLET_MISMATCH', 'FORGE_PLATFORM_FEE_WALLET must be the revenue wallet (FORGE_REVENUE_WALLET)', { details: { platformFeeWallet: a.FORGE_PLATFORM_FEE_WALLET.toBase58() } });
|
|
870
|
+
}
|
|
781
871
|
return {
|
|
782
872
|
platformFeeWallet: a.FORGE_PLATFORM_FEE_WALLET,
|
|
783
873
|
referralTokenAccount: a.FORGE_METEORA_REFERRAL_ACCOUNT,
|
|
874
|
+
revenueWallet,
|
|
784
875
|
};
|
|
785
876
|
}
|
|
786
877
|
function slippageOf(p) {
|
|
@@ -791,7 +882,10 @@ async function buildSide(side, amountIn, p) {
|
|
|
791
882
|
assertU64(side === 'buy' ? 'amountInLamports' : 'amountInTokens', amountIn, 1n);
|
|
792
883
|
const slippage = readSlippage(slippageOf(p));
|
|
793
884
|
const options = readBuildOptions(p);
|
|
794
|
-
const
|
|
885
|
+
const priority = readPrioritySetting(p);
|
|
886
|
+
const { platformFeeWallet, referralTokenAccount, revenueWallet } = swapFeeAddresses(p.env);
|
|
887
|
+
if (p.allowedConfigs !== undefined)
|
|
888
|
+
toConfigKeys(p.allowedConfigs);
|
|
795
889
|
const ctx = await fetchSwapContext({
|
|
796
890
|
connection: p.connection,
|
|
797
891
|
pool: p.pool,
|
|
@@ -799,8 +893,27 @@ async function buildSide(side, amountIn, p) {
|
|
|
799
893
|
platformFeeWallet,
|
|
800
894
|
referralTokenAccount,
|
|
801
895
|
...(p.commitment ? { commitment: p.commitment } : {}),
|
|
896
|
+
...(p.allowedConfigs !== undefined ? { allowedConfigs: p.allowedConfigs } : {}),
|
|
802
897
|
});
|
|
803
|
-
|
|
898
|
+
const expected = { platformFeeWallet, referralOwner: revenueWallet };
|
|
899
|
+
const built = buildSwapTransaction(ctx, { side, amountIn, slippage, ...options }, expected);
|
|
900
|
+
if (priority === undefined)
|
|
901
|
+
return built;
|
|
902
|
+
// Same swap with its price: read for the accounts it write-locks (the pool, its vaults and the
|
|
903
|
+
// FORGE fee accounts are shared by every trade), bounded by the setting.
|
|
904
|
+
const quote = await estimatePriorityFee({
|
|
905
|
+
connection: p.connection,
|
|
906
|
+
writableAccounts: writableStaticKeys(built.transaction),
|
|
907
|
+
computeUnitLimit: options.computeUnitLimit,
|
|
908
|
+
setting: priority,
|
|
909
|
+
});
|
|
910
|
+
const priced = buildSwapTransaction(ctx, { side, amountIn, slippage, ...options, priorityFeeMicroLamports: quote.microLamports }, expected);
|
|
911
|
+
return { ...priced, priorityFee: quote };
|
|
912
|
+
}
|
|
913
|
+
/** Writable static account keys of a compiled v0 transaction, in message order. */
|
|
914
|
+
function writableStaticKeys(transaction) {
|
|
915
|
+
const message = transaction.message;
|
|
916
|
+
return message.staticAccountKeys.filter((_, index) => message.isAccountWritable(index));
|
|
804
917
|
}
|
|
805
918
|
/** Unsigned buy transaction (SOL -> coin) with the FORGE referral and the platform fee. */
|
|
806
919
|
export async function buildBuyTx(params) {
|
|
@@ -819,18 +932,19 @@ export async function quoteSwap(params) {
|
|
|
819
932
|
const slippage = params.slippageBps === undefined && params.minAmountOut === undefined
|
|
820
933
|
? { slippageBps: 0 }
|
|
821
934
|
: readSlippage(slippageOf(params));
|
|
822
|
-
const state = await fetchSwapPoolState(params.connection, params.pool, params.commitment);
|
|
935
|
+
const state = await fetchSwapPoolState(params.connection, params.pool, params.commitment, params.allowedConfigs !== undefined ? { allowedConfigs: params.allowedConfigs } : {});
|
|
823
936
|
return computeSwapQuote(state, { side: params.side, amountIn: params.amountIn, slippage });
|
|
824
937
|
}
|
|
825
938
|
/**
|
|
826
|
-
* Startup / pre-build check of the FORGE fee accounts: the platform fee wallet must be
|
|
827
|
-
* and the referral account must be a wrapped SOL token account
|
|
939
|
+
* Startup / pre-build check of the FORGE fee accounts: the platform fee wallet must be a
|
|
940
|
+
* rent-exempt system account and the referral account must be a wrapped SOL token account, held by
|
|
941
|
+
* `FORGE_REVENUE_WALLET` when the environment has it. Throws `SwapError`.
|
|
828
942
|
*/
|
|
829
943
|
export async function assertSwapFeeAccountsReady(params) {
|
|
830
944
|
const commitment = params.commitment ?? DEFAULT_COMMITMENT;
|
|
831
|
-
const { platformFeeWallet, referralTokenAccount } = swapFeeAddresses(params.env);
|
|
945
|
+
const { platformFeeWallet, referralTokenAccount, revenueWallet } = swapFeeAddresses(params.env);
|
|
832
946
|
const [platformInfo, referralInfo] = (await rpc('getMultipleAccountsInfo', () => params.connection.getMultipleAccountsInfo([platformFeeWallet, referralTokenAccount], commitment))).map(snapshot);
|
|
833
947
|
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);
|
|
948
|
+
assertPlatformFeeWalletReady(platformFeeWallet, platformInfo ?? null, rentMinimum, platformFeeWallet);
|
|
949
|
+
assertReferralAccountReady(referralTokenAccount, referralInfo ?? null, revenueWallet);
|
|
836
950
|
}
|
package/dist/tx.d.ts
CHANGED
|
@@ -182,6 +182,8 @@ export declare function readNonce(connection: Pick<Connection, 'getAccountInfo'>
|
|
|
182
182
|
expectedAuthority?: PublicKey;
|
|
183
183
|
commitment?: Commitment;
|
|
184
184
|
}): Promise<NonceInfo>;
|
|
185
|
+
/** Compute unit limit of the nonce account creations when a priority fee is set. */
|
|
186
|
+
export declare const CREATE_NONCE_ACCOUNT_COMPUTE_UNITS = 10000;
|
|
185
187
|
export interface BuildCreateNonceAccountTxParams {
|
|
186
188
|
connection: Pick<Connection, 'getMinimumBalanceForRentExemption' | 'getLatestBlockhash'>;
|
|
187
189
|
/** Pays the rent and the fee; signs. */
|
package/dist/tx.js
CHANGED
|
@@ -390,6 +390,8 @@ export async function readNonce(connection, nonceAccount, options = {}) {
|
|
|
390
390
|
lamportsPerSignature: BigInt(decoded.feeCalculator.lamportsPerSignature),
|
|
391
391
|
};
|
|
392
392
|
}
|
|
393
|
+
/** Compute unit limit of the nonce account creations when a priority fee is set. */
|
|
394
|
+
export const CREATE_NONCE_ACCOUNT_COMPUTE_UNITS = 10_000;
|
|
393
395
|
/**
|
|
394
396
|
* Unsigned transaction creating and initializing a durable nonce account
|
|
395
397
|
* (`SystemProgram.createNonceAccount`). The rent is read from the cluster.
|
|
@@ -404,7 +406,7 @@ export async function buildCreateNonceAccountTx(params) {
|
|
|
404
406
|
const transaction = new Transaction({ feePayer: params.payer, blockhash, lastValidBlockHeight });
|
|
405
407
|
if (params.computeUnitPriceMicroLamports !== undefined) {
|
|
406
408
|
transaction.add(...computeBudgetInstructions({
|
|
407
|
-
units:
|
|
409
|
+
units: CREATE_NONCE_ACCOUNT_COMPUTE_UNITS,
|
|
408
410
|
microLamports: params.computeUnitPriceMicroLamports,
|
|
409
411
|
}));
|
|
410
412
|
}
|
|
@@ -475,7 +477,7 @@ export async function buildCreateNonceAccountWithSeedTx(params) {
|
|
|
475
477
|
const nonceAccount = await deriveSeededNonceAccount(base, seed);
|
|
476
478
|
const budget = params.computeUnitPriceMicroLamports !== undefined
|
|
477
479
|
? computeBudgetInstructions({
|
|
478
|
-
units:
|
|
480
|
+
units: CREATE_NONCE_ACCOUNT_COMPUTE_UNITS,
|
|
479
481
|
microLamports: params.computeUnitPriceMicroLamports,
|
|
480
482
|
})
|
|
481
483
|
: [];
|
package/dist/validate.d.ts
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`.
|
|
@@ -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'
|
|
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
|
-
|
|
355
|
-
|
|
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(
|
|
358
|
-
|
|
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
|
-
|
|
361
|
-
|
|
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.
|
|
3
|
+
"version": "0.3.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.
|
|
50
|
+
"@forge/shared": "npm:@forgedevv/shared@0.3.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",
|