@forgedevv/core 0.2.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 +142 -2
- package/dist/addresses.d.ts +25 -6
- package/dist/addresses.js +27 -5
- package/dist/claims.d.ts +31 -4
- package/dist/claims.js +46 -5
- package/dist/config.d.ts +8 -0
- package/dist/config.js +17 -5
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +20 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +12 -6
- package/dist/jupiter-referral.d.ts +219 -4
- package/dist/jupiter-referral.js +382 -11
- package/dist/launch-metadata.d.ts +2 -0
- 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 +40 -0
- package/dist/pool.js +128 -24
- package/dist/position-claims.d.ts +9 -1
- package/dist/position-claims.js +21 -4
- 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/swap.d.ts +35 -16
- package/dist/swap.js +68 -19
- package/dist/tx.d.ts +2 -0
- package/dist/tx.js +4 -2
- package/package.json +2 -2
package/dist/reads.js
CHANGED
|
@@ -165,6 +165,19 @@ export async function readCoin(connection, pool, options = {}) {
|
|
|
165
165
|
* @throws DbcReadError when the config does not exist.
|
|
166
166
|
*/
|
|
167
167
|
export async function listCoinsOfConfig(connection, config, options = {}) {
|
|
168
|
+
return (await listPoolStatesOfConfig(connection, config, options)).map(summarizeCoin);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The decoded pools of a config with the config itself (what `listCoinsOfConfig` summarizes):
|
|
172
|
+
* one `getPoolsByConfig` scan (one `getProgramAccounts` per pool account variant, filtered on the
|
|
173
|
+
* config at offset 72) and one config read. A pool whose decoded `config` is not `config` is
|
|
174
|
+
* dropped (a wrong RPC answer is never shown). Sorted by pool address.
|
|
175
|
+
*
|
|
176
|
+
* The scan returns every pool of the config: callers should cache it (the FORGE RPC relay serves
|
|
177
|
+
* it to server-token callers only, with a response size cap).
|
|
178
|
+
* @throws DbcReadError when the config does not exist.
|
|
179
|
+
*/
|
|
180
|
+
export async function listPoolStatesOfConfig(connection, config, options = {}) {
|
|
168
181
|
const state = stateService(connection, options.commitment ?? READ_COMMITMENT);
|
|
169
182
|
const configState = await retryRpc(() => state.getPoolConfig(config), options.retry);
|
|
170
183
|
if (!configState) {
|
|
@@ -173,8 +186,32 @@ export async function listCoinsOfConfig(connection, config, options = {}) {
|
|
|
173
186
|
const pools = await retryRpc(() => state.getPoolsByConfig(config), options.retry);
|
|
174
187
|
return pools
|
|
175
188
|
.filter((p) => p.account.poolState.config.equals(config))
|
|
176
|
-
.map((p) =>
|
|
177
|
-
.sort((a, b) => compareStrings(a.
|
|
189
|
+
.map((p) => ({ address: p.publicKey, virtualPool: p.account, config: configState }))
|
|
190
|
+
.sort((a, b) => compareStrings(a.address.toBase58(), b.address.toBase58()));
|
|
191
|
+
}
|
|
192
|
+
/** 2^128: a pool's `sqrtPrice` is a Q64.64 number, so its square is a Q128.128 price. */
|
|
193
|
+
const Q128 = 1n << 128n;
|
|
194
|
+
/** Fixed-point scale used to turn the bigint price into a float without losing small prices. */
|
|
195
|
+
const SPOT_PRICE_SCALE = 10n ** 18n;
|
|
196
|
+
/**
|
|
197
|
+
* Pure: spot price of one whole base token (the coin) in whole quote tokens (SOL for FORGE pools),
|
|
198
|
+
* from the pool's `sqrtPrice`: `sqrtPrice^2 / 2^128` quote units per base unit (the price the
|
|
199
|
+
* curve is at, before fees), times `10^(baseDecimals - quoteDecimals)`. A float, for display only:
|
|
200
|
+
* quotes and transactions use the exact bigint math of `quoteSwap`.
|
|
201
|
+
*/
|
|
202
|
+
export function poolSpotPrice(sqrtPrice, baseDecimals, quoteDecimals = 9) {
|
|
203
|
+
const sqrt = toBigint(sqrtPrice);
|
|
204
|
+
if (sqrt <= 0n)
|
|
205
|
+
return 0;
|
|
206
|
+
const shift = baseDecimals - quoteDecimals;
|
|
207
|
+
let numerator = sqrt * sqrt * SPOT_PRICE_SCALE;
|
|
208
|
+
let denominator = Q128;
|
|
209
|
+
if (shift >= 0)
|
|
210
|
+
numerator *= 10n ** BigInt(shift);
|
|
211
|
+
else
|
|
212
|
+
denominator *= 10n ** BigInt(-shift);
|
|
213
|
+
const scaled = numerator / denominator;
|
|
214
|
+
return Number(scaled) / Number(SPOT_PRICE_SCALE);
|
|
178
215
|
}
|
|
179
216
|
/**
|
|
180
217
|
* Byte offset of `creator` in a DBC pool account (`VirtualPool` and `TransferHookPool`: 8-byte
|
package/dist/swap.d.ts
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);
|
|
@@ -31,20 +32,22 @@
|
|
|
31
32
|
* site only trades its own coins;
|
|
32
33
|
* - the platform fee wallet does not exist or is not rent-exempt
|
|
33
34
|
* (`PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT`): the whole swap would fail on-chain (devnet finding);
|
|
34
|
-
* - the platform fee wallet is not exactly `FORGE_PLATFORM_FEE_WALLET`, or not a system account
|
|
35
|
-
*
|
|
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`);
|
|
36
38
|
* - the referral account is not an initialized wrapped SOL token account, or is not held by
|
|
37
|
-
* `
|
|
39
|
+
* `FORGE_REVENUE_WALLET` when the environment has it (`REFERRAL_ACCOUNT_INVALID`);
|
|
38
40
|
* - the pool is not a plain SOL-quoted DBC pool (`UNSUPPORTED_POOL`; transfer-hook pools excluded);
|
|
39
41
|
* - the serialized transaction exceeds 1,232 bytes (`TRANSACTION_TOO_LARGE`).
|
|
40
42
|
*
|
|
41
43
|
* Transaction size: measured on devnet (scripts/devnet-tests/reports/core-swap.md) at 819 bytes for a
|
|
42
44
|
* buy and 797 bytes for a sell with this exact layout, far below the 1,232-byte limit (a buy that
|
|
43
|
-
* also creates the coin ATA adds 10 bytes
|
|
44
|
-
*
|
|
45
|
-
* `AddressLookupTableAccount`s, e.g. one holding the
|
|
46
|
-
*
|
|
47
|
-
* 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.
|
|
48
51
|
*
|
|
49
52
|
* Layering: `fetchSwapContext` does all the network reads (with retries); everything else is pure
|
|
50
53
|
* and unit-tested without network (`buildSwapTransaction`, `computeSwapQuote`, fee math).
|
|
@@ -53,6 +56,7 @@ import { PublicKey, TransactionInstruction, VersionedTransaction, type AddressLo
|
|
|
53
56
|
import BN from 'bn.js';
|
|
54
57
|
import { type PoolConfig, type VirtualPool } from '@meteora-ag/dynamic-bonding-curve-sdk';
|
|
55
58
|
import { type AddressEnv } from './addresses.js';
|
|
59
|
+
import { type PriorityFeeQuote, type PriorityFeeSetting } from './priority-fee.js';
|
|
56
60
|
/** Platform fee on swaps built by @forge/core, in bps (`FEES.platformFeeBps` from @forge/shared). */
|
|
57
61
|
export declare const PLATFORM_FEE_BPS: number;
|
|
58
62
|
/** Highest slippage tolerance accepted by the builders (25%), whether given in bps or as a minimum. */
|
|
@@ -284,7 +288,7 @@ export interface SwapContext extends SwapPoolState {
|
|
|
284
288
|
export declare function assertPlatformFeeWalletReady(wallet: PublicKey, account: SwapAccountSnapshot | null, rentExemptMinimum: bigint, expectedWallet?: PublicKey | null): void;
|
|
285
289
|
/**
|
|
286
290
|
* Throws unless the referral account is an initialized wrapped SOL token account (SPL Token).
|
|
287
|
-
* With `expectedOwner` (
|
|
291
|
+
* With `expectedOwner` (FORGE_REVENUE_WALLET, when the environment has it), the token account must
|
|
288
292
|
* be held by that key: otherwise the referral share would be paid to someone else.
|
|
289
293
|
*/
|
|
290
294
|
export declare function assertReferralAccountReady(address: PublicKey, account: SwapAccountSnapshot | null, expectedOwner?: PublicKey | null): void;
|
|
@@ -292,7 +296,7 @@ export declare function assertReferralAccountReady(address: PublicKey, account:
|
|
|
292
296
|
export interface SwapFeeAccountExpectations {
|
|
293
297
|
/** The platform fee transfer must go to exactly this key (FORGE_PLATFORM_FEE_WALLET). */
|
|
294
298
|
platformFeeWallet?: PublicKey | null;
|
|
295
|
-
/** The Meteora referral token account must be held by this key (
|
|
299
|
+
/** The Meteora referral token account must be held by this key (FORGE_REVENUE_WALLET). */
|
|
296
300
|
referralOwner?: PublicKey | null;
|
|
297
301
|
}
|
|
298
302
|
/**
|
|
@@ -305,6 +309,13 @@ export interface SwapBuildOptions {
|
|
|
305
309
|
computeUnitLimit?: number;
|
|
306
310
|
/** Priority fee in micro-lamports per CU (default 0: no `setComputeUnitPrice` instruction). */
|
|
307
311
|
priorityFeeMicroLamports?: number;
|
|
312
|
+
/**
|
|
313
|
+
* Priority fee read from the cluster (priority-fee.ts): the recent fees of the swap's writable
|
|
314
|
+
* accounts, bounded by this setting (`{}` = `DEFAULT_PRIORITY_FEE_POLICY`: at most 0.0005 SOL
|
|
315
|
+
* per swap). Only `buildBuyTx` / `buildSellTx` read it (they hold the connection); not with
|
|
316
|
+
* `priorityFeeMicroLamports`.
|
|
317
|
+
*/
|
|
318
|
+
priority?: PriorityFeeSetting;
|
|
308
319
|
/**
|
|
309
320
|
* Address lookup tables to compile the v0 message with, if the transaction would exceed 1,232
|
|
310
321
|
* bytes without them (it does not with the current layout). Fetch them with
|
|
@@ -324,6 +335,11 @@ export interface BuiltSwapTransaction {
|
|
|
324
335
|
sizeBytes: number;
|
|
325
336
|
computeUnitLimit: number;
|
|
326
337
|
priorityFeeMicroLamports: number;
|
|
338
|
+
/**
|
|
339
|
+
* The priority fee: price, fee in lamports and its cap (`maxLamports`, the cost guard to show
|
|
340
|
+
* before signing), and where the price comes from (`fixed` without a `priority` setting).
|
|
341
|
+
*/
|
|
342
|
+
priorityFee: PriorityFeeQuote;
|
|
327
343
|
/** Base fee + priority fee, in lamports. */
|
|
328
344
|
estimatedNetworkFeeLamports: bigint;
|
|
329
345
|
/** Rent locked in a coin ATA created by a buy (0 when it already existed or on a sell). */
|
|
@@ -376,13 +392,15 @@ export interface FetchSwapContextParams {
|
|
|
376
392
|
export declare function fetchSwapContext(p: FetchSwapContextParams): Promise<SwapContext>;
|
|
377
393
|
/**
|
|
378
394
|
* FORGE fee accounts used by the swap, resolved through addresses.ts (never hard-coded), and the
|
|
379
|
-
*
|
|
380
|
-
* `
|
|
395
|
+
* revenue wallet that must hold the referral account (`null` when the environment does not have
|
|
396
|
+
* `FORGE_REVENUE_WALLET`: the owner check is then skipped). When the environment has it, the
|
|
397
|
+
* platform fee wallet must be that same wallet (D34: the platform fee is revenue too), otherwise
|
|
398
|
+
* `PLATFORM_FEE_WALLET_MISMATCH`.
|
|
381
399
|
*/
|
|
382
400
|
export declare function swapFeeAddresses(env?: AddressEnv): {
|
|
383
401
|
platformFeeWallet: PublicKey;
|
|
384
402
|
referralTokenAccount: PublicKey;
|
|
385
|
-
|
|
403
|
+
revenueWallet: PublicKey | null;
|
|
386
404
|
};
|
|
387
405
|
interface SwapCommonParams extends SwapBuildOptions {
|
|
388
406
|
connection: Connection;
|
|
@@ -392,7 +410,8 @@ interface SwapCommonParams extends SwapBuildOptions {
|
|
|
392
410
|
/**
|
|
393
411
|
* Environment to read the FORGE addresses from (defaults to `process.env`):
|
|
394
412
|
* `FORGE_PLATFORM_FEE_WALLET` and `FORGE_METEORA_REFERRAL_ACCOUNT` (required), and
|
|
395
|
-
* `
|
|
413
|
+
* `FORGE_REVENUE_WALLET` (optional: when set, the referral account must be held by it and the
|
|
414
|
+
* platform fee wallet must be it).
|
|
396
415
|
*/
|
|
397
416
|
env?: AddressEnv;
|
|
398
417
|
commitment?: Commitment;
|
|
@@ -436,7 +455,7 @@ export declare function quoteSwap(params: QuoteSwapParams): Promise<SwapQuote>;
|
|
|
436
455
|
/**
|
|
437
456
|
* Startup / pre-build check of the FORGE fee accounts: the platform fee wallet must be a
|
|
438
457
|
* rent-exempt system account and the referral account must be a wrapped SOL token account, held by
|
|
439
|
-
* `
|
|
458
|
+
* `FORGE_REVENUE_WALLET` when the environment has it. Throws `SwapError`.
|
|
440
459
|
*/
|
|
441
460
|
export declare function assertSwapFeeAccountsReady(params: {
|
|
442
461
|
connection: Connection;
|
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);
|
|
@@ -31,20 +32,22 @@
|
|
|
31
32
|
* site only trades its own coins;
|
|
32
33
|
* - the platform fee wallet does not exist or is not rent-exempt
|
|
33
34
|
* (`PLATFORM_FEE_WALLET_NOT_RENT_EXEMPT`): the whole swap would fail on-chain (devnet finding);
|
|
34
|
-
* - the platform fee wallet is not exactly `FORGE_PLATFORM_FEE_WALLET`, or not a system account
|
|
35
|
-
*
|
|
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`);
|
|
36
38
|
* - the referral account is not an initialized wrapped SOL token account, or is not held by
|
|
37
|
-
* `
|
|
39
|
+
* `FORGE_REVENUE_WALLET` when the environment has it (`REFERRAL_ACCOUNT_INVALID`);
|
|
38
40
|
* - the pool is not a plain SOL-quoted DBC pool (`UNSUPPORTED_POOL`; transfer-hook pools excluded);
|
|
39
41
|
* - the serialized transaction exceeds 1,232 bytes (`TRANSACTION_TOO_LARGE`).
|
|
40
42
|
*
|
|
41
43
|
* Transaction size: measured on devnet (scripts/devnet-tests/reports/core-swap.md) at 819 bytes for a
|
|
42
44
|
* buy and 797 bytes for a sell with this exact layout, far below the 1,232-byte limit (a buy that
|
|
43
|
-
* also creates the coin ATA adds 10 bytes
|
|
44
|
-
*
|
|
45
|
-
* `AddressLookupTableAccount`s, e.g. one holding the
|
|
46
|
-
*
|
|
47
|
-
* 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.
|
|
48
51
|
*
|
|
49
52
|
* Layering: `fetchSwapContext` does all the network reads (with retries); everything else is pure
|
|
50
53
|
* and unit-tested without network (`buildSwapTransaction`, `computeSwapQuote`, fee math).
|
|
@@ -55,6 +58,7 @@ import BN from 'bn.js';
|
|
|
55
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';
|
|
56
59
|
import { readOptionalAddress, requireAddresses } from './addresses.js';
|
|
57
60
|
import { FEES } from './constants.js';
|
|
61
|
+
import { estimatePriorityFee, fixedPriorityFee, resolvePriorityFeePolicy, } from './priority-fee.js';
|
|
58
62
|
// ---------------------------------------------------------------------------------------------
|
|
59
63
|
// Constants
|
|
60
64
|
// ---------------------------------------------------------------------------------------------
|
|
@@ -454,7 +458,7 @@ export function assertPlatformFeeWalletReady(wallet, account, rentExemptMinimum,
|
|
|
454
458
|
}
|
|
455
459
|
/**
|
|
456
460
|
* Throws unless the referral account is an initialized wrapped SOL token account (SPL Token).
|
|
457
|
-
* With `expectedOwner` (
|
|
461
|
+
* With `expectedOwner` (FORGE_REVENUE_WALLET, when the environment has it), the token account must
|
|
458
462
|
* be held by that key: otherwise the referral share would be paid to someone else.
|
|
459
463
|
*/
|
|
460
464
|
export function assertReferralAccountReady(address, account, expectedOwner) {
|
|
@@ -478,7 +482,7 @@ export function assertReferralAccountReady(address, account, expectedOwner) {
|
|
|
478
482
|
if (!token.isInitialized || token.isFrozen)
|
|
479
483
|
throw fail('is not usable (uninitialized or frozen)');
|
|
480
484
|
if (expectedOwner && !token.owner.equals(expectedOwner)) {
|
|
481
|
-
throw fail('is not held by
|
|
485
|
+
throw fail('is not held by FORGE_REVENUE_WALLET');
|
|
482
486
|
}
|
|
483
487
|
}
|
|
484
488
|
function toConfigKeys(allowedConfigs) {
|
|
@@ -529,6 +533,21 @@ function readBuildOptions(o) {
|
|
|
529
533
|
throw invalid('addressLookupTables must be an array');
|
|
530
534
|
return { computeUnitLimit, priorityFeeMicroLamports, addressLookupTables };
|
|
531
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
|
+
}
|
|
532
551
|
/**
|
|
533
552
|
* Compiles the instructions into an unsigned v0 transaction paid by `payer` and checks its
|
|
534
553
|
* serialized size (signature placeholders included) against 1,232 bytes.
|
|
@@ -573,6 +592,10 @@ export function compileSwapTransaction(args) {
|
|
|
573
592
|
* the top of this file.
|
|
574
593
|
*/
|
|
575
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
|
+
}
|
|
576
599
|
const options = readBuildOptions(request);
|
|
577
600
|
assertPlatformFeeWalletReady(ctx.platformFeeWallet, ctx.platformFeeWalletAccount, ctx.platformFeeWalletRentExemptMinimum, expected.platformFeeWallet);
|
|
578
601
|
assertReferralAccountReady(ctx.referralTokenAccount, ctx.referralTokenAccountInfo, expected.referralOwner);
|
|
@@ -669,6 +692,7 @@ export function buildSwapTransaction(ctx, request, expected = {}) {
|
|
|
669
692
|
sizeBytes,
|
|
670
693
|
computeUnitLimit: options.computeUnitLimit,
|
|
671
694
|
priorityFeeMicroLamports: options.priorityFeeMicroLamports,
|
|
695
|
+
priorityFee: fixedPriorityFee(options.computeUnitLimit, options.priorityFeeMicroLamports),
|
|
672
696
|
estimatedNetworkFeeLamports,
|
|
673
697
|
newCoinAccountRentLamports,
|
|
674
698
|
requiredLamports,
|
|
@@ -833,15 +857,21 @@ export async function fetchSwapContext(p) {
|
|
|
833
857
|
}
|
|
834
858
|
/**
|
|
835
859
|
* FORGE fee accounts used by the swap, resolved through addresses.ts (never hard-coded), and the
|
|
836
|
-
*
|
|
837
|
-
* `
|
|
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`.
|
|
838
864
|
*/
|
|
839
865
|
export function swapFeeAddresses(env) {
|
|
840
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
|
+
}
|
|
841
871
|
return {
|
|
842
872
|
platformFeeWallet: a.FORGE_PLATFORM_FEE_WALLET,
|
|
843
873
|
referralTokenAccount: a.FORGE_METEORA_REFERRAL_ACCOUNT,
|
|
844
|
-
|
|
874
|
+
revenueWallet,
|
|
845
875
|
};
|
|
846
876
|
}
|
|
847
877
|
function slippageOf(p) {
|
|
@@ -852,7 +882,8 @@ async function buildSide(side, amountIn, p) {
|
|
|
852
882
|
assertU64(side === 'buy' ? 'amountInLamports' : 'amountInTokens', amountIn, 1n);
|
|
853
883
|
const slippage = readSlippage(slippageOf(p));
|
|
854
884
|
const options = readBuildOptions(p);
|
|
855
|
-
const
|
|
885
|
+
const priority = readPrioritySetting(p);
|
|
886
|
+
const { platformFeeWallet, referralTokenAccount, revenueWallet } = swapFeeAddresses(p.env);
|
|
856
887
|
if (p.allowedConfigs !== undefined)
|
|
857
888
|
toConfigKeys(p.allowedConfigs);
|
|
858
889
|
const ctx = await fetchSwapContext({
|
|
@@ -864,7 +895,25 @@ async function buildSide(side, amountIn, p) {
|
|
|
864
895
|
...(p.commitment ? { commitment: p.commitment } : {}),
|
|
865
896
|
...(p.allowedConfigs !== undefined ? { allowedConfigs: p.allowedConfigs } : {}),
|
|
866
897
|
});
|
|
867
|
-
|
|
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));
|
|
868
917
|
}
|
|
869
918
|
/** Unsigned buy transaction (SOL -> coin) with the FORGE referral and the platform fee. */
|
|
870
919
|
export async function buildBuyTx(params) {
|
|
@@ -889,13 +938,13 @@ export async function quoteSwap(params) {
|
|
|
889
938
|
/**
|
|
890
939
|
* Startup / pre-build check of the FORGE fee accounts: the platform fee wallet must be a
|
|
891
940
|
* rent-exempt system account and the referral account must be a wrapped SOL token account, held by
|
|
892
|
-
* `
|
|
941
|
+
* `FORGE_REVENUE_WALLET` when the environment has it. Throws `SwapError`.
|
|
893
942
|
*/
|
|
894
943
|
export async function assertSwapFeeAccountsReady(params) {
|
|
895
944
|
const commitment = params.commitment ?? DEFAULT_COMMITMENT;
|
|
896
|
-
const { platformFeeWallet, referralTokenAccount,
|
|
945
|
+
const { platformFeeWallet, referralTokenAccount, revenueWallet } = swapFeeAddresses(params.env);
|
|
897
946
|
const [platformInfo, referralInfo] = (await rpc('getMultipleAccountsInfo', () => params.connection.getMultipleAccountsInfo([platformFeeWallet, referralTokenAccount], commitment))).map(snapshot);
|
|
898
947
|
const rentMinimum = BigInt(await rpc('getMinimumBalanceForRentExemption', () => params.connection.getMinimumBalanceForRentExemption(platformInfo?.data.length ?? 0, commitment)));
|
|
899
948
|
assertPlatformFeeWalletReady(platformFeeWallet, platformInfo ?? null, rentMinimum, platformFeeWallet);
|
|
900
|
-
assertReferralAccountReady(referralTokenAccount, referralInfo ?? null,
|
|
949
|
+
assertReferralAccountReady(referralTokenAccount, referralInfo ?? null, revenueWallet);
|
|
901
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/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,7 @@
|
|
|
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
51
|
"@meteora-ag/cp-amm-sdk": "1.5.1",
|
|
52
52
|
"@meteora-ag/dynamic-bonding-curve-sdk": "1.5.13",
|
|
53
53
|
"@solana/spl-token": "0.4.15",
|