@forgedevv/core 0.2.0 → 0.3.1

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 CHANGED
@@ -3,9 +3,159 @@
3
3
  All notable changes to `@forge/core`. Versions follow semantic versioning. Consumers (the
4
4
  launchpad template, the signer, the web app) pin an exact version, never a range.
5
5
 
6
- ## 0.2.0 — unreleased
6
+ ## 0.3.1 — 2026-10-17
7
7
 
8
- Everything below was added after 0.1.0 was published. Publish `@forgedevv/shared` 0.2.0 with it.
8
+ ### Fixed
9
+
10
+ - Client sites failed to build with 0.3.0 (`node:crypto` in `jupiter-referral.js` and
11
+ `meteora-referral.js`, refused by the Next.js browser bundle). Both now hash with
12
+ `@noble/hashes` (new exact dependency 1.8.0, already used by `@solana/web3.js`); the
13
+ discriminators and derived addresses are unchanged. A test refuses any Node built-in
14
+ import in the package sources.
15
+
16
+ ## 0.3.0 — 2026-10-17
17
+
18
+ Changes after 0.2.0. Publish `@forgedevv/shared` 0.3.0 with it (new kill switch `revenue_paused` in `FlagKey`).
19
+
20
+ ### Changed (Jupiter fixes)
21
+
22
+ - **Jupiter referral fee.** `FEES.jupiterIntegratorFeeBps` (re-exported from `@forge/shared`) is
23
+ 50 bps, was 30: the Jupiter Plugin passes `referralFee` to Ultra `/order`, which refuses any
24
+ value outside 50..255 bps, so every swap of a graduated coin failed. Jupiter keeps 20% of it.
25
+ `FEES.platformFeeBps` (30 bps, `PLATFORM_FEE_BPS` on swaps built by core) is unchanged.
26
+ - **Jupiter referral claims.** `buildJupiterReferralClaimTx` refuses a referral account of another
27
+ project than Jupiter's Ultra project (`project_mismatch`; the new `expectedProject` parameter
28
+ overrides the default `JUPITER_ULTRA_REFERRAL_PROJECT`). A funded v2 collection account is only
29
+ claimed for a named referral account at the PDA `["referral", project, name]`, the only kind
30
+ `claim_v2` accepts: `referral_account_unnamed` or `referral_account_not_pda` otherwise (new
31
+ `JupiterReferralErrorCode` values; the whole build is refused). With an empty v2 account, an
32
+ unnamed account still claims its v1 collection account. Devnet is unchanged: any cluster but
33
+ mainnet-beta is still refused (`wrong_cluster`) before these reads.
34
+
35
+ ### Added (Jupiter fixes)
36
+
37
+ - `JUPITER_REFERRAL_FEE_BPS_RANGE` (`{ min: 50, max: 255 }`) and
38
+ `assertJupiterReferralFeeBps(bps)` (constants.ts): returns a whole number of bps within the range
39
+ Jupiter Ultra accepts, throws a `RangeError` otherwise. The launchpad template runs the plugin
40
+ fee through it and fails closed outside it.
41
+ - `JUPITER_ULTRA_REFERRAL_PROJECT` (`DkiqsTrw1u1bYFumumC7sCG2S8K25qc2vemJFHyW2wJc`) and
42
+ `deriveJupiterReferralAccount(project, name, programId?)` (jupiter-referral.ts).
43
+
44
+ ### Added (revenue wallet referral accounts setup)
45
+
46
+ Pure builders of the two accounts the revenue wallet must hold (docs/MAINNET.md section 5), for
47
+ the signer's one-off `--setup-revenue-referrals` command. Only the payer signs: neither the
48
+ revenue wallet nor the new accounts do.
49
+
50
+ - **`meteora-referral.ts`** (re-exported by the index): `deriveMeteoraReferralAccount(payer,
51
+ seed?)` (`createWithSeed(payer, seed, SPL Token)`, default seed
52
+ `METEORA_REFERRAL_ACCOUNT_SEED` = `forge-meteora-referral`), `buildCreateMeteoraReferralAccountIxs`
53
+ / `buildCreateMeteoraReferralAccountTx({ payer, owner, seed?, rentLamports, blockhash,
54
+ lastValidBlockHeight, priorityFee? })`: System `createAccountWithSeed` (from = base = payer,
55
+ the rent of 165 bytes, SPL Token owned) then SPL Token `InitializeAccount3` (wrapped SOL,
56
+ owner = the revenue wallet, no owner signature), the compute budget pair first when the quote
57
+ has a price (`METEORA_REFERRAL_SETUP_COMPUTE_UNITS` = 30,000); `inspectMeteoraReferralAccount`
58
+ (pure: `absent`, `ready`, or `mismatch` with a reason: not a token account, wrong mint or owner,
59
+ the owner's associated account, frozen, delegated, closable by someone else) and
60
+ `readMeteoraReferralAccount` (any cluster); `MeteoraReferralError` (`invalid_params`).
61
+ - **Jupiter referral setup** (jupiter-referral.ts), verified against the program source
62
+ (`initialize_referral_account_with_name.rs`, `lib.rs`, `claim_v2.rs`) and the SDK 0.3.0 IDL:
63
+ `JUPITER_INITIALIZE_REFERRAL_ACCOUNT_WITH_NAME_DISCRIMINATOR`, `JUPITER_REFERRAL_ACCOUNT_SIZE`
64
+ (274), `JUPITER_REFERRAL_NAME_MAX_CHARS` (19: the program requires fewer than 20 characters)
65
+ and `JUPITER_REFERRAL_NAME_MAX_BYTES` (32: a PDA seed), `assertJupiterReferralName` (also
66
+ refuses whitespace and control characters), `encodeJupiterReferralName` (Borsh),
67
+ `buildJupiterInitializeReferralAccountWithNameIx({ payer, partner, project, referralAccount },
68
+ name)` (the IDL's accounts: payer the only signer, partner read-only and not a signer, the
69
+ referral account the PDA `["referral", project, name]`),
70
+ `buildJupiterReferralTokenAccountV2Ix({ payer, referralAccount, mint, tokenProgram })` (the v2
71
+ collection account `claim_v2` empties is the referral account's associated token account: an
72
+ idempotent creation, what the SDK's `initializeReferralTokenAccountV2` sends),
73
+ `buildJupiterReferralSetupTx({ payer, partner, project?, name, createReferralAccount,
74
+ tokenAccounts, blockhash, lastValidBlockHeight, priorityFee? })` (Ultra project by default,
75
+ `JUPITER_REFERRAL_SETUP_COMPUTE_UNITS` = 150,000), `inspectJupiterReferralAccount` /
76
+ `inspectJupiterReferralTokenAccountV2` (pure statuses with mismatch reasons: partner, project,
77
+ name, owner, mint) and `readJupiterReferralSetup({ connection, partner, name, mints })`
78
+ (mainnet-beta only, `wrong_cluster` before any read; reads the project, the mints' token
79
+ programs, the referral account and its collection accounts).
80
+
81
+ ### Changed (revenue wallet, docs/DECISIONS.md D34)
82
+
83
+ - **New address `FORGE_REVENUE_WALLET`** (`FORGE_ADDRESS_NAMES`, `getRevenueWallet`): the
84
+ signer-held wallet where the non-native trading revenue lands before the signer converts it to
85
+ SOL and forwards it to the multisig vault. It replaces the vault as the holder of the Meteora
86
+ referral account and as the partner of the Jupiter referral account; the platform fee goes to
87
+ it too (`FORGE_PLATFORM_FEE_WALLET` must be that wallet).
88
+ - **Swaps.** `swapFeeAddresses` returns `revenueWallet` (was `multisigVault`), read from
89
+ `FORGE_REVENUE_WALLET` (optional, as before): when set, the Meteora referral token account must
90
+ be held by it (`REFERRAL_ACCOUNT_INVALID`, `assertReferralAccountReady(..., expectedOwner?)`)
91
+ and `FORGE_PLATFORM_FEE_WALLET` must be it (`PLATFORM_FEE_WALLET_MISMATCH`, checked before any
92
+ network call by `buildBuyTx`, `buildSellTx` and `assertSwapFeeAccountsReady`). Client sites
93
+ receive `NEXT_PUBLIC_FORGE_REVENUE_WALLET` instead of `NEXT_PUBLIC_FORGE_MULTISIG_VAULT`.
94
+ - `buildJupiterReferralClaimTx` is unchanged: `expectedPartner` is now the revenue wallet.
95
+
96
+ ### Added (priority fees)
97
+
98
+ - **`priority-fee.ts`** (browser-safe, re-exported by the index): `computePriorityFee` (pure: the
99
+ nearest-rank `percentile` of `getRecentPrioritizationFees`, at least `floorMicroLamports`, at
100
+ most `maxMicroLamports` per CU and `maxLamports` for the whole transaction; an empty list or a
101
+ failed read prices at the floor, reported in `source`), `estimatePriorityFee` (reads the fees
102
+ for the transaction's writable accounts, at most `MAX_PRIORITY_FEE_ACCOUNTS` = 32),
103
+ `readRecentPriorityFees`, `resolvePriorityFeePolicy`, `feePercentile`, `maxPriceForBudget`,
104
+ `maxPriorityFeeLamports` (the worst case of a setting, to show before building),
105
+ `fixedPriorityFee`, `priorityFeeInstructions` (unit limit then price; `priceOnly`),
106
+ `writableAccountsOf`, and `checkComputeBudgetInstructions` / `ComputeBudgetCheckError` for
107
+ transaction policies (no account, `SetComputeUnitLimit` / `SetComputeUnitPrice` only, at most
108
+ one of each, bounded values and fee; without a limit the fee is counted for 1.4M CU).
109
+ `DEFAULT_PRIORITY_FEE_POLICY`: 75th percentile, floor 10,000 micro-lamports per CU, at most
110
+ 1,000,000 per CU and 500,000 lamports (0.0005 SOL) per transaction; hard ceilings
111
+ `MAX_PRIORITY_FEE_MICRO_LAMPORTS` (10 lamports per CU) and `MAX_PRIORITY_FEE_LAMPORTS`
112
+ (0.01 SOL). Types `PriorityFeeSetting`, `PriorityFeePolicy`, `PriorityFeeQuote`,
113
+ `PriorityFeeSource`, `RecentPrioritizationFee`.
114
+ - **Builders accept an optional `priority` setting** (exclusive with their fixed price, if any),
115
+ which puts `setComputeUnitLimit` then `setComputeUnitPrice` first (after the nonce advance):
116
+ `buildBuyTx` / `buildSellTx` (`SwapBuildOptions.priority`; the pure `buildSwapTransaction`
117
+ refuses it), `buildPaymentTx` (`PaymentTxOptions.priority`, limit `PAYMENT_COMPUTE_UNITS` =
118
+ 50,000), `buildLaunchCoinTx` and `buildUserCoinLaunchTx`, `buildCreateConfigTx`, the creator,
119
+ partner and pool creation fee claims (`ClaimOptions.priority`, limits `CLAIM_COMPUTE_UNITS` =
120
+ 200,000 and `POOL_CREATION_FEE_CLAIM_COMPUTE_UNITS` = 100,000), the DAMM v2 position claim
121
+ (`positionClaimComputeUnits`) and `buildJupiterReferralClaimTx`
122
+ (`JUPITER_REFERRAL_CLAIM_COMPUTE_UNITS` = 300,000). Their results report the quote
123
+ (`priorityFee`), and the claim costs count the fee. `CREATE_NONCE_ACCOUNT_COMPUTE_UNITS`
124
+ (10,000) names the limit of the nonce account builders.
125
+ - **Launch sizes.** The launchpad coin launch keeps the 148-byte metadata budget: with a priority
126
+ fee it carries the unit limit and the price up to 136 bytes of metadata, the price alone up to
127
+ 144 (priced for `MAX_TRANSACTION_COMPUTE_UNITS`), and no price beyond
128
+ (`LaunchCoinSummary.priorityFeePlacement`: `limit_and_price`, `price_only`,
129
+ `omitted_for_size`). A priced swap or user coin launch is 12 bytes larger.
130
+
131
+ ### Changed (priority fees)
132
+
133
+ - `BuiltSwapTransaction.priorityFee`, `PaymentTxResult.priorityFee`, `CreateConfigTx.priorityFee`,
134
+ `ClaimSummary.priorityFee`, `PoolCreationFeeClaimSummary.priorityFee`,
135
+ `PositionClaimSummary.priorityFee`, `JupiterReferralClaimTx.priorityFee` and
136
+ `LaunchCoinSummary` / `UserCoinLaunchSummary` `.priorityFee` and `.priorityFeePlacement` are new
137
+ required fields: code that builds these objects itself (test fakes) must set them. Without a
138
+ `priority` setting every transaction is byte for byte what 0.2.0 built.
139
+ - `ClaimCosts.networkFeeLamports` and `PositionClaimCosts.networkFeeLamports` include the
140
+ priority fee (0 without a setting).
141
+ - Consumers: the launchpad template prices its trades, coin creations and creator fee claims with
142
+ the defaults, so client sites need this version, and the FORGE RPC relay must serve
143
+ `getRecentPrioritizationFees` (apps/web, same release).
144
+
145
+ ### Added (on-chain fallback of the coin data)
146
+
147
+ - `listPoolStatesOfConfig(connection, config, options?)` (reads.ts): the decoded pools of a config
148
+ with the config state (`DbcPoolState[]`, sorted by pool address), from the SDK's
149
+ `getPoolsByConfig`; a pool whose decoded config differs is dropped. `listCoinsOfConfig` now
150
+ summarizes its result (unchanged output). The launchpad template lists its coins with it when
151
+ the Jupiter data API is unavailable (and always on devnet).
152
+ - `poolSpotPrice(sqrtPrice, baseDecimals, quoteDecimals = 9)` (reads.ts): display price of one
153
+ whole coin in whole quote tokens from the pool's Q64.64 `sqrtPrice` (same value as the SDK's
154
+ `getPriceFromSqrtPrice`, as a float).
155
+
156
+ ## 0.2.0 — 2026-10-17
157
+
158
+ Everything below was added after 0.1.0 and published on npm on 2026-10-17 with `@forgedevv/shared` 0.2.0.
9
159
 
10
160
  ### Changed (security review)
11
161
 
@@ -9,14 +9,20 @@
9
9
  * Validation choice: a value is valid when the `PublicKey` constructor accepts it (base58 that
10
10
  * decodes to exactly 32 bytes) and it re-encodes to the same string. Being on the ed25519 curve is
11
11
  * NOT required, because some of these addresses are token accounts or PDAs (e.g. the Meteora
12
- * referral account, which is a WSOL token account held by the multisig, or a Squads vault PDA).
12
+ * referral account, which is a WSOL token account held by the revenue wallet, or a Squads vault
13
+ * PDA).
14
+ *
15
+ * The revenue wallet (`FORGE_REVENUE_WALLET`, docs/DECISIONS.md D34) is the signer-held wallet
16
+ * where the non-native trading revenue lands: the platform fee (`FORGE_PLATFORM_FEE_WALLET` must
17
+ * be that wallet), the Meteora referral WSOL account it holds, and the Jupiter referral claims
18
+ * (it is the referral account's partner). The signer converts and forwards it to the vault.
13
19
  * The all-zero key (`11111111111111111111111111111111`, the System Program) is rejected: fees
14
20
  * sent there would be lost.
15
21
  *
16
22
  * Error messages list variable names only, never values.
17
23
  */
18
24
  import { PublicKey } from '@solana/web3.js';
19
- export declare const FORGE_ADDRESS_NAMES: readonly ["FORGE_MULTISIG_VAULT", "FORGE_METEORA_REFERRAL_ACCOUNT", "FORGE_PLATFORM_FEE_WALLET", "FORGE_JUPITER_REFERRAL_ACCOUNT", "FORGE_CASHBOX_WALLET", "FORGE_MINT", "FORGE_SQUADS_MULTISIG", "FORGE_SQUADS_SPENDING_LIMIT"];
25
+ export declare const FORGE_ADDRESS_NAMES: readonly ["FORGE_MULTISIG_VAULT", "FORGE_METEORA_REFERRAL_ACCOUNT", "FORGE_PLATFORM_FEE_WALLET", "FORGE_REVENUE_WALLET", "FORGE_JUPITER_REFERRAL_ACCOUNT", "FORGE_CASHBOX_WALLET", "FORGE_MINT", "FORGE_SQUADS_MULTISIG", "FORGE_SQUADS_SPENDING_LIMIT"];
20
26
  export type ForgeAddressName = (typeof FORGE_ADDRESS_NAMES)[number];
21
27
  /**
22
28
  * Addresses that may be absent. `FORGE_MINT` is absent before the $FORGE launch; the Squads
@@ -48,17 +54,30 @@ export declare function requireAddresses<N extends ForgeAddressName>(names: read
48
54
  * One FORGE address when it is set, `null` when it is absent or empty. A value that is set but is
49
55
  * not a valid public key is refused, exactly as `requireAddresses` does. For checks that run
50
56
  * wherever the address is known and are skipped elsewhere (e.g. the owner of the Meteora referral
51
- * account, `FORGE_MULTISIG_VAULT`, in a browser that was not given it).
57
+ * account, `FORGE_REVENUE_WALLET`, in a browser that was not given it).
52
58
  *
53
59
  * @throws ForgeAddressError (invalid value; names only).
54
60
  */
55
61
  export declare function readOptionalAddress(name: ForgeAddressName, env?: AddressEnv): PublicKey | null;
56
- /** Squads multisig vault: receives the creator fees, the Meteora referral and the cashbox sweeps. */
62
+ /**
63
+ * Squads multisig vault: receives the creator fees, the cashbox sweeps and the revenue wallet's
64
+ * forwards (the converted trading revenue).
65
+ */
57
66
  export declare function getMultisigVault(env?: AddressEnv): PublicKey;
58
- /** Meteora referral token account (WSOL, held by the multisig), passed as `referralTokenAccount`. */
67
+ /** Meteora referral token account (WSOL, held by the revenue wallet), passed as `referralTokenAccount`. */
59
68
  export declare function getMeteoraReferralAccount(env?: AddressEnv): PublicKey;
60
- /** Wallet receiving the 0.3% platform fee on swaps built by @forge/core. */
69
+ /**
70
+ * Wallet receiving the 0.3% platform fee on swaps built by @forge/core: the revenue wallet
71
+ * (`FORGE_REVENUE_WALLET`, D34). Kept as its own variable so the fee destination stays explicit;
72
+ * `swapFeeAddresses` refuses an environment where the two differ.
73
+ */
61
74
  export declare function getPlatformFeeWallet(env?: AddressEnv): PublicKey;
75
+ /**
76
+ * Revenue wallet (D34): held by the signer (keystore role `revenue`), it receives the platform
77
+ * fee, holds the Meteora referral WSOL account and is the partner of the Jupiter referral
78
+ * account. The signer regularly converts what it holds to SOL and forwards it to the vault.
79
+ */
80
+ export declare function getRevenueWallet(env?: AddressEnv): PublicKey;
62
81
  /** Jupiter referral account (integrator fee after graduation). */
63
82
  export declare function getJupiterReferralAccount(env?: AddressEnv): PublicKey;
64
83
  /** Cashbox wallet (held by the signer) receiving client payments. */
package/dist/addresses.js CHANGED
@@ -9,7 +9,13 @@
9
9
  * Validation choice: a value is valid when the `PublicKey` constructor accepts it (base58 that
10
10
  * decodes to exactly 32 bytes) and it re-encodes to the same string. Being on the ed25519 curve is
11
11
  * NOT required, because some of these addresses are token accounts or PDAs (e.g. the Meteora
12
- * referral account, which is a WSOL token account held by the multisig, or a Squads vault PDA).
12
+ * referral account, which is a WSOL token account held by the revenue wallet, or a Squads vault
13
+ * PDA).
14
+ *
15
+ * The revenue wallet (`FORGE_REVENUE_WALLET`, docs/DECISIONS.md D34) is the signer-held wallet
16
+ * where the non-native trading revenue lands: the platform fee (`FORGE_PLATFORM_FEE_WALLET` must
17
+ * be that wallet), the Meteora referral WSOL account it holds, and the Jupiter referral claims
18
+ * (it is the referral account's partner). The signer converts and forwards it to the vault.
13
19
  * The all-zero key (`11111111111111111111111111111111`, the System Program) is rejected: fees
14
20
  * sent there would be lost.
15
21
  *
@@ -20,6 +26,7 @@ export const FORGE_ADDRESS_NAMES = [
20
26
  'FORGE_MULTISIG_VAULT',
21
27
  'FORGE_METEORA_REFERRAL_ACCOUNT',
22
28
  'FORGE_PLATFORM_FEE_WALLET',
29
+ 'FORGE_REVENUE_WALLET',
23
30
  'FORGE_JUPITER_REFERRAL_ACCOUNT',
24
31
  'FORGE_CASHBOX_WALLET',
25
32
  'FORGE_MINT',
@@ -112,7 +119,7 @@ export function requireAddresses(names, env = defaultEnv()) {
112
119
  * One FORGE address when it is set, `null` when it is absent or empty. A value that is set but is
113
120
  * not a valid public key is refused, exactly as `requireAddresses` does. For checks that run
114
121
  * wherever the address is known and are skipped elsewhere (e.g. the owner of the Meteora referral
115
- * account, `FORGE_MULTISIG_VAULT`, in a browser that was not given it).
122
+ * account, `FORGE_REVENUE_WALLET`, in a browser that was not given it).
116
123
  *
117
124
  * @throws ForgeAddressError (invalid value; names only).
118
125
  */
@@ -122,18 +129,33 @@ export function readOptionalAddress(name, env = defaultEnv()) {
122
129
  return null;
123
130
  return requireAddresses([name], env)[name];
124
131
  }
125
- /** Squads multisig vault: receives the creator fees, the Meteora referral and the cashbox sweeps. */
132
+ /**
133
+ * Squads multisig vault: receives the creator fees, the cashbox sweeps and the revenue wallet's
134
+ * forwards (the converted trading revenue).
135
+ */
126
136
  export function getMultisigVault(env) {
127
137
  return requireAddresses(['FORGE_MULTISIG_VAULT'], env).FORGE_MULTISIG_VAULT;
128
138
  }
129
- /** Meteora referral token account (WSOL, held by the multisig), passed as `referralTokenAccount`. */
139
+ /** Meteora referral token account (WSOL, held by the revenue wallet), passed as `referralTokenAccount`. */
130
140
  export function getMeteoraReferralAccount(env) {
131
141
  return requireAddresses(['FORGE_METEORA_REFERRAL_ACCOUNT'], env).FORGE_METEORA_REFERRAL_ACCOUNT;
132
142
  }
133
- /** Wallet receiving the 0.3% platform fee on swaps built by @forge/core. */
143
+ /**
144
+ * Wallet receiving the 0.3% platform fee on swaps built by @forge/core: the revenue wallet
145
+ * (`FORGE_REVENUE_WALLET`, D34). Kept as its own variable so the fee destination stays explicit;
146
+ * `swapFeeAddresses` refuses an environment where the two differ.
147
+ */
134
148
  export function getPlatformFeeWallet(env) {
135
149
  return requireAddresses(['FORGE_PLATFORM_FEE_WALLET'], env).FORGE_PLATFORM_FEE_WALLET;
136
150
  }
151
+ /**
152
+ * Revenue wallet (D34): held by the signer (keystore role `revenue`), it receives the platform
153
+ * fee, holds the Meteora referral WSOL account and is the partner of the Jupiter referral
154
+ * account. The signer regularly converts what it holds to SOL and forwards it to the vault.
155
+ */
156
+ export function getRevenueWallet(env) {
157
+ return requireAddresses(['FORGE_REVENUE_WALLET'], env).FORGE_REVENUE_WALLET;
158
+ }
137
159
  /** Jupiter referral account (integrator fee after graduation). */
138
160
  export function getJupiterReferralAccount(env) {
139
161
  return requireAddresses(['FORGE_JUPITER_REFERRAL_ACCOUNT'], env).FORGE_JUPITER_REFERRAL_ACCOUNT;
package/dist/claims.d.ts CHANGED
@@ -1,8 +1,17 @@
1
- import { PublicKey, Transaction, type Commitment, type Connection } from '@solana/web3.js';
1
+ import { PublicKey, Transaction, type Commitment, type Connection, type TransactionInstruction } from '@solana/web3.js';
2
2
  import { type AddressEnv } from './addresses.js';
3
+ import { type PriorityFeeQuote, type PriorityFeeSetting } from './priority-fee.js';
3
4
  import { type DbcPoolState, type RetryOptions, type TokenAmounts } from './reads.js';
4
- /** Base fee per signature (lamports); claims set no priority fee. */
5
+ /** Base fee per signature (lamports); a priority fee comes on top with `ClaimOptions.priority`. */
5
6
  export declare const CLAIM_LAMPORTS_PER_SIGNATURE = 5000n;
7
+ /**
8
+ * Compute unit limit of a trading fee claim with a priority fee: two idempotent token account
9
+ * creations (about 25,000 CU each when they create), the DBC claim (two token transfers and its
10
+ * event), a token self-transfer, a close and a SOL transfer, about 100,000 CU at most: 2x margin.
11
+ */
12
+ export declare const CLAIM_COMPUTE_UNITS = 200000;
13
+ /** Compute unit limit of a pool creation fee claim (one DBC instruction moving lamports). */
14
+ export declare const POOL_CREATION_FEE_CLAIM_COMPUTE_UNITS = 100000;
6
15
  export { PARTNER_POOL_CREATION_FEE_CLAIMED_BIT, partnerPoolCreationFeeShare } from './reads.js';
7
16
  export type ClaimErrorCode = 'not_pool_creator' | 'not_fee_claimer' | 'unsupported_quote_mint' | 'invalid_receiver' | 'receiver_mismatch' | 'receiver_not_rent_exempt' | 'claimer_wsol_not_empty' | 'unexpected_sdk_instructions' | 'not_migrated' | 'damm_pool_not_found' | 'damm_pool_mismatch' | 'position_not_found' | 'position_mismatch' | 'position_not_owned';
8
17
  export declare class ClaimError extends Error {
@@ -12,7 +21,7 @@ export declare class ClaimError extends Error {
12
21
  export type ClaimKind = 'creator' | 'partner';
13
22
  /** Lamport costs of a trading fee claim, as bigint. */
14
23
  export interface ClaimCosts {
15
- /** Signature fees (signers x 5000 lamports; no priority fee), paid by the payer. */
24
+ /** Signature fees (signers x 5000 lamports) plus the priority fee, if any; paid by the payer. */
16
25
  networkFeeLamports: bigint;
17
26
  /**
18
27
  * Rent of the receiver's coin account, created on the first claim of a pool (paid by the payer,
@@ -56,6 +65,8 @@ export interface ClaimSummary {
56
65
  * costs are paid (negative when it costs more than it collects). Base tokens are not valued.
57
66
  */
58
67
  netLamports: bigint;
68
+ /** The priority fee of the transaction (price, fee, cap), or null without one. */
69
+ priorityFee: PriorityFeeQuote | null;
59
70
  }
60
71
  export type ClaimSkipReason = 'nothing_to_claim' | 'not_worthwhile';
61
72
  export interface ClaimTxResult {
@@ -75,7 +86,21 @@ export interface ClaimOptions {
75
86
  * than this many lamports (see `isClaimWorthwhile`). Default: never skip a non-empty claim.
76
87
  */
77
88
  minNetLamports?: bigint;
89
+ /**
90
+ * Priority fee read from the cluster for the claim's writable accounts, bounded by this setting
91
+ * (`{}` = `DEFAULT_PRIORITY_FEE_POLICY`, at most 0.0005 SOL). Default: none.
92
+ */
93
+ priority?: PriorityFeeSetting;
78
94
  }
95
+ /** Checks `options.priority` before any network call. @throws RangeError. */
96
+ export declare function assertClaimPriority(options: {
97
+ priority?: PriorityFeeSetting;
98
+ }): void;
99
+ /** The priced compute budget of a claim, or nothing without `priority`. */
100
+ export declare function claimPriorityFee(connection: Connection, instructions: readonly TransactionInstruction[], computeUnitLimit: number, priority: PriorityFeeSetting | undefined): Promise<{
101
+ quote: PriorityFeeQuote | null;
102
+ instructions: TransactionInstruction[];
103
+ }>;
79
104
  export interface CreatorClaimParams {
80
105
  pool: PublicKey;
81
106
  /** Pool creator (must match the pool state); signs the claim. */
@@ -163,10 +188,12 @@ export interface PoolCreationFeeClaimSummary {
163
188
  alreadyClaimed: boolean;
164
189
  /** SOL the receiver gets from the transaction (0 when there is none), lamports. */
165
190
  claimableLamports: bigint;
166
- /** Signature fees of the transaction (0 when there is none), paid by the payer. */
191
+ /** Signature fees plus the priority fee of the transaction (0 when there is none). */
167
192
  networkFeeLamports: bigint;
168
193
  /** `claimableLamports - networkFeeLamports` (0 when there is no transaction). */
169
194
  netLamports: bigint;
195
+ /** The priority fee of the transaction (price, fee, cap), or null without one. */
196
+ priorityFee: PriorityFeeQuote | null;
170
197
  }
171
198
  export type PoolCreationFeeSkipReason = 'no_pool_creation_fee' | 'already_claimed';
172
199
  export interface PoolCreationFeeClaimTxResult {
package/dist/claims.js CHANGED
@@ -34,6 +34,12 @@
34
34
  * claim (FORGE configs collect fees in SOL only). It is the receiver's associated account, created
35
35
  * idempotently by the payer: the first claim of each pool costs the payer that account's rent
36
36
  * (~0.00204 SOL), which stays in an account owned by the receiver (recoverable by closing it).
37
+ *
38
+ * Priority fee (`ClaimOptions.priority`, priority-fee.ts): two compute budget instructions come
39
+ * first, `setComputeUnitLimit` (`CLAIM_COMPUTE_UNITS`, or `POOL_CREATION_FEE_CLAIM_COMPUTE_UNITS`)
40
+ * then `setComputeUnitPrice`, priced from the recent fees of the claim's writable accounts and
41
+ * bounded by the setting; the costs (network fee, worthwhile check) count it. Without the option
42
+ * the claims carry no compute budget instruction, as before.
37
43
  */
38
44
  import { ACCOUNT_SIZE, NATIVE_MINT, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, createCloseAccountInstruction, createTransferInstruction, getAssociatedTokenAddressSync, unpackAccount, } from '@solana/spl-token';
39
45
  import { PublicKey, SystemProgram, Transaction, } from '@solana/web3.js';
@@ -41,9 +47,18 @@ import { CreatorService, DYNAMIC_BONDING_CURVE_PROGRAM_ID, PartnerService, Token
41
47
  import BN from 'bn.js';
42
48
  import { getMultisigVault } from './addresses.js';
43
49
  import { SOL_QUOTE_MINT } from './constants.js';
50
+ import { estimatePriorityFee, priorityFeeInstructions, resolvePriorityFeePolicy, writableAccountsOf, } from './priority-fee.js';
44
51
  import { READ_COMMITMENT, feeMetrics, getDbcPoolState, poolCreationFeeStatus, retryRpc, } from './reads.js';
45
- /** Base fee per signature (lamports); claims set no priority fee. */
52
+ /** Base fee per signature (lamports); a priority fee comes on top with `ClaimOptions.priority`. */
46
53
  export const CLAIM_LAMPORTS_PER_SIGNATURE = 5000n;
54
+ /**
55
+ * Compute unit limit of a trading fee claim with a priority fee: two idempotent token account
56
+ * creations (about 25,000 CU each when they create), the DBC claim (two token transfers and its
57
+ * event), a token self-transfer, a close and a SOL transfer, about 100,000 CU at most: 2x margin.
58
+ */
59
+ export const CLAIM_COMPUTE_UNITS = 200_000;
60
+ /** Compute unit limit of a pool creation fee claim (one DBC instruction moving lamports). */
61
+ export const POOL_CREATION_FEE_CLAIM_COMPUTE_UNITS = 100_000;
47
62
  // Pool creation fee helpers live in reads.ts (CoinOfConfig uses them); re-exported here.
48
63
  export { PARTNER_POOL_CREATION_FEE_CLAIMED_BIT, partnerPoolCreationFeeShare } from './reads.js';
49
64
  export class ClaimError extends Error {
@@ -54,6 +69,23 @@ export class ClaimError extends Error {
54
69
  this.code = code;
55
70
  }
56
71
  }
72
+ /** Checks `options.priority` before any network call. @throws RangeError. */
73
+ export function assertClaimPriority(options) {
74
+ if (options.priority !== undefined)
75
+ resolvePriorityFeePolicy(options.priority);
76
+ }
77
+ /** The priced compute budget of a claim, or nothing without `priority`. */
78
+ export async function claimPriorityFee(connection, instructions, computeUnitLimit, priority) {
79
+ if (priority === undefined)
80
+ return { quote: null, instructions: [] };
81
+ const quote = await estimatePriorityFee({
82
+ connection,
83
+ writableAccounts: writableAccountsOf(instructions),
84
+ computeUnitLimit,
85
+ setting: priority,
86
+ });
87
+ return { quote, instructions: priorityFeeInstructions(quote) };
88
+ }
57
89
  // ---------------------------------------------------------------------------------------------
58
90
  // Pure helpers
59
91
  // ---------------------------------------------------------------------------------------------
@@ -123,6 +155,7 @@ async function buildTradingClaim(connection, input, options) {
123
155
  existingWsolLamports: 0n,
124
156
  costs: ZERO_COSTS,
125
157
  netLamports: 0n,
158
+ priorityFee: null,
126
159
  },
127
160
  };
128
161
  }
@@ -189,12 +222,13 @@ async function buildTradingClaim(connection, input, options) {
189
222
  if (exact && quote > 0n) {
190
223
  instructions.push(SystemProgram.transfer({ fromPubkey: payer, toPubkey: receiver, lamports: quote }));
191
224
  }
225
+ const priority = await claimPriorityFee(connection, instructions, CLAIM_COMPUTE_UNITS, options.priority);
192
226
  const { blockhash, lastValidBlockHeight } = await retryRpc(() => connection.getLatestBlockhash(commitment), options.retry);
193
227
  const transaction = new Transaction({ feePayer: payer, blockhash, lastValidBlockHeight });
194
- transaction.add(...instructions);
228
+ transaction.add(...priority.instructions, ...instructions);
195
229
  // 4. Costs and net.
196
230
  const signatures = BigInt(transaction.compileMessage().header.numRequiredSignatures);
197
- const networkFeeLamports = signatures * CLAIM_LAMPORTS_PER_SIGNATURE;
231
+ const networkFeeLamports = signatures * CLAIM_LAMPORTS_PER_SIGNATURE + (priority.quote?.priorityFeeLamports ?? 0n);
198
232
  const receiverTokenAccountRentLamports = receiverBaseInfo ? 0n : BigInt(tokenAccountRent);
199
233
  const temporaryLamports = claimerWsolInfo ? 0n : BigInt(tokenAccountRent);
200
234
  const payerTotalLamports = networkFeeLamports + receiverTokenAccountRentLamports;
@@ -212,6 +246,7 @@ async function buildTradingClaim(connection, input, options) {
212
246
  existingWsolLamports,
213
247
  costs,
214
248
  netLamports: receiverQuoteLamports - payerTotalLamports,
249
+ priorityFee: priority.quote,
215
250
  };
216
251
  if (options.minNetLamports !== undefined && !isClaimWorthwhile(summary, options.minNetLamports)) {
217
252
  return { transaction: null, lastValidBlockHeight: null, skipReason: 'not_worthwhile', summary };
@@ -227,6 +262,7 @@ async function buildTradingClaim(connection, input, options) {
227
262
  export async function buildCreatorClaimTx(connection, params, options = {}) {
228
263
  const commitment = options.commitment ?? READ_COMMITMENT;
229
264
  assertReceiver(params.receiver);
265
+ assertClaimPriority(options);
230
266
  if (params.expectedReceiver && !params.expectedReceiver.equals(params.receiver)) {
231
267
  throw new ClaimError('receiver_mismatch', `Creator fees of pool ${params.pool.toBase58()} must go to ${params.expectedReceiver.toBase58()}, not ${params.receiver.toBase58()}`);
232
268
  }
@@ -278,6 +314,7 @@ export async function buildPartnerClaimTx(connection, params, options = {}) {
278
314
  const commitment = options.commitment ?? READ_COMMITMENT;
279
315
  const receiver = params.receiver ?? params.feeClaimer;
280
316
  assertReceiver(receiver);
317
+ assertClaimPriority(options);
281
318
  const state = await getDbcPoolState(connection, params.pool, {
282
319
  commitment,
283
320
  retry: options.retry,
@@ -317,6 +354,7 @@ export async function buildPartnerPoolCreationFeeClaimTx(connection, params, opt
317
354
  const commitment = options.commitment ?? READ_COMMITMENT;
318
355
  const receiver = params.receiver ?? params.feeClaimer;
319
356
  assertReceiver(receiver);
357
+ assertClaimPriority(options);
320
358
  const state = await getDbcPoolState(connection, params.pool, {
321
359
  commitment,
322
360
  retry: options.retry,
@@ -341,6 +379,7 @@ export async function buildPartnerPoolCreationFeeClaimTx(connection, params, opt
341
379
  claimableLamports: 0n,
342
380
  networkFeeLamports: 0n,
343
381
  netLamports: 0n,
382
+ priorityFee: null,
344
383
  };
345
384
  if (partnerShareLamports === 0n || alreadyClaimed) {
346
385
  return {
@@ -366,11 +405,12 @@ export async function buildPartnerPoolCreationFeeClaimTx(connection, params, opt
366
405
  !claimIx.keys.some((k) => k.isWritable && k.pubkey.equals(receiver))) {
367
406
  throw new ClaimError('unexpected_sdk_instructions', 'The SDK pool creation fee claim does not have the expected instruction');
368
407
  }
408
+ const priority = await claimPriorityFee(connection, [claimIx], POOL_CREATION_FEE_CLAIM_COMPUTE_UNITS, options.priority);
369
409
  const { blockhash, lastValidBlockHeight } = await retryRpc(() => connection.getLatestBlockhash(commitment), options.retry);
370
410
  const transaction = new Transaction({ feePayer: payer, blockhash, lastValidBlockHeight });
371
- transaction.add(claimIx);
411
+ transaction.add(...priority.instructions, claimIx);
372
412
  const signatures = BigInt(transaction.compileMessage().header.numRequiredSignatures);
373
- const networkFeeLamports = signatures * CLAIM_LAMPORTS_PER_SIGNATURE;
413
+ const networkFeeLamports = signatures * CLAIM_LAMPORTS_PER_SIGNATURE + (priority.quote?.priorityFeeLamports ?? 0n);
374
414
  return {
375
415
  transaction,
376
416
  lastValidBlockHeight,
@@ -380,6 +420,7 @@ export async function buildPartnerPoolCreationFeeClaimTx(connection, params, opt
380
420
  claimableLamports: partnerShareLamports,
381
421
  networkFeeLamports,
382
422
  netLamports: partnerShareLamports - networkFeeLamports,
423
+ priorityFee: priority.quote,
383
424
  },
384
425
  };
385
426
  }
package/dist/config.d.ts CHANGED
@@ -28,6 +28,7 @@
28
28
  import { type BuildCurveParams, type ConfigParameters, type PoolConfig, type SwapQuoteConfig } from '@meteora-ag/dynamic-bonding-curve-sdk';
29
29
  import { PublicKey, Transaction, type Connection } from '@solana/web3.js';
30
30
  import { LaunchpadSpec } from '@forge/shared';
31
+ import { type PriorityFeeQuote, type PriorityFeeSetting } from './priority-fee.js';
31
32
  export { ANTI_SNIPER_FEE_SCHEDULE, FORGE_ACTIVATION_TYPE, FORGE_MIGRATION_SETTINGS, FORGE_TOKEN_SETTINGS, FORGE_TOKEN_SUPPLY_RAW, forgeBaseFee, forgeFeeSchedulerParam, forgeReferenceCurve, type ForgeReferenceCurve, } from './settings.js';
32
33
  /**
33
34
  * Pool creation fee of the launchpad coin config: none. The only pool ever created on it is the
@@ -170,6 +171,11 @@ export interface BuildCreateConfigTxParams {
170
171
  payer: PublicKey;
171
172
  /** Priority fee, in micro-lamports per compute unit (default 0). */
172
173
  computeUnitPriceMicroLamports?: number;
174
+ /**
175
+ * Priority fee read from the cluster for the payer and the config account, bounded by this
176
+ * setting (priority-fee.ts). Not with `computeUnitPriceMicroLamports`.
177
+ */
178
+ priority?: PriorityFeeSetting;
173
179
  /** Blockhash to use; fetched from the cluster when absent. */
174
180
  recentBlockhash?: {
175
181
  blockhash: string;
@@ -187,6 +193,8 @@ export interface CreateConfigTx {
187
193
  lastValidBlockHeight: number;
188
194
  /** Who must sign: the payer (fee payer) and the config keypair. */
189
195
  signers: PublicKey[];
196
+ /** The priority fee (price, fee, cap; `fixed` without a `priority` setting). */
197
+ priorityFee: PriorityFeeQuote;
190
198
  }
191
199
  /**
192
200
  * Unsigned transaction creating one of the two configs (`createConfig`). The parameters are
package/dist/config.js CHANGED
@@ -32,7 +32,7 @@ import { LaunchpadSpec } from '@forge/shared';
32
32
  import { CLIENT_BOUNDS, CLIENT_POOL_CREATION_FEE_LAMPORTS, FEES, LAMPORTS_PER_SOL_BIGINT, SOL_QUOTE_MINT, solToLamports, } from './constants.js';
33
33
  import { LaunchCoinError, assertSpecLaunchable } from './launch-metadata.js';
34
34
  import { ANTI_SNIPER_FEE_SCHEDULE, FORGE_ACTIVATION_TYPE, FORGE_MIGRATION_SETTINGS, FORGE_TOKEN_SETTINGS, forgeFeeSchedulerParam, } from './settings.js';
35
- import { computeBudgetInstructions } from './tx.js';
35
+ import { estimatePriorityFee, fixedPriorityFee, priorityFeeInstructions, resolvePriorityFeePolicy, } from './priority-fee.js';
36
36
  import { ValidationError, assertValid, validateLaunchpadCoinConfigParams, validateLaunchpadConfigParams, } from './validate.js';
37
37
  // ---------------------------------------------------------------------------------------------
38
38
  // Settings
@@ -570,6 +570,12 @@ export class CreateConfigError extends Error {
570
570
  */
571
571
  export async function buildCreateConfigTx(params) {
572
572
  const configAddress = params.config instanceof PublicKey ? params.config : params.config.publicKey;
573
+ if (params.priority !== undefined) {
574
+ if (params.computeUnitPriceMicroLamports !== undefined) {
575
+ throw new RangeError('pass either priority or computeUnitPriceMicroLamports, not both');
576
+ }
577
+ resolvePriorityFeePolicy(params.priority);
578
+ }
573
579
  const configParams = buildConfigParams(params.spec, params.kind, params.metadataUri !== undefined ? { metadataUri: params.metadataUri } : {});
574
580
  if (configAddress.equals(params.payer) ||
575
581
  configAddress.equals(configParams.feeClaimer) ||
@@ -588,11 +594,16 @@ export async function buildCreateConfigTx(params) {
588
594
  });
589
595
  const rentLamports = BigInt(await connection.getMinimumBalanceForRentExemption(client.partner.getProgram().account.poolConfig.size));
590
596
  const { blockhash, lastValidBlockHeight } = params.recentBlockhash ?? (await connection.getLatestBlockhash('confirmed'));
597
+ const priorityFee = params.priority === undefined
598
+ ? fixedPriorityFee(CREATE_CONFIG_COMPUTE_UNITS, params.computeUnitPriceMicroLamports ?? 0)
599
+ : await estimatePriorityFee({
600
+ connection,
601
+ writableAccounts: [params.payer, configAddress],
602
+ computeUnitLimit: CREATE_CONFIG_COMPUTE_UNITS,
603
+ setting: params.priority,
604
+ });
591
605
  const transaction = new Transaction({ feePayer: params.payer, blockhash, lastValidBlockHeight });
592
- transaction.add(...computeBudgetInstructions({
593
- units: CREATE_CONFIG_COMPUTE_UNITS,
594
- microLamports: params.computeUnitPriceMicroLamports ?? 0,
595
- }), ...sdkTx.instructions);
606
+ transaction.add(...priorityFeeInstructions(priorityFee), ...sdkTx.instructions);
596
607
  return {
597
608
  transaction,
598
609
  configAddress,
@@ -600,5 +611,6 @@ export async function buildCreateConfigTx(params) {
600
611
  rentLamports,
601
612
  lastValidBlockHeight,
602
613
  signers: [params.payer, configAddress],
614
+ priorityFee,
603
615
  };
604
616
  }
@@ -45,3 +45,19 @@ export declare const CLIENT_POOL_CREATION_FEE_LAMPORTS: {
45
45
  readonly max: bigint;
46
46
  readonly allowZero: true;
47
47
  };
48
+ /**
49
+ * Referral fee range of Jupiter Ultra, in bps. The Jupiter Plugin passes `referralFee` as is to
50
+ * Ultra `/order`, which refuses any value outside 50..255 ("must be between 50 and 255
51
+ * (inclusive)", checked on 2026-10-17); Jupiter keeps 20% of the fee.
52
+ * @see https://developers.jup.ag/docs/swap/fees
53
+ */
54
+ export declare const JUPITER_REFERRAL_FEE_BPS_RANGE: {
55
+ readonly min: 50;
56
+ readonly max: 255;
57
+ };
58
+ /**
59
+ * Returns `bps` when Jupiter Ultra accepts it as a referral fee: a whole number of bps within
60
+ * `JUPITER_REFERRAL_FEE_BPS_RANGE`. Throws a `RangeError` otherwise: with such a fee Jupiter
61
+ * refuses every swap, so it must never reach the plugin.
62
+ */
63
+ export declare function assertJupiterReferralFeeBps(bps: number): number;
package/dist/constants.js CHANGED
@@ -68,3 +68,23 @@ export const CLIENT_POOL_CREATION_FEE_LAMPORTS = {
68
68
  max: solToLamports(CLIENT_BOUNDS.poolCreationFeeSol.max),
69
69
  allowZero: CLIENT_BOUNDS.poolCreationFeeSol.allowZero,
70
70
  };
71
+ /**
72
+ * Referral fee range of Jupiter Ultra, in bps. The Jupiter Plugin passes `referralFee` as is to
73
+ * Ultra `/order`, which refuses any value outside 50..255 ("must be between 50 and 255
74
+ * (inclusive)", checked on 2026-10-17); Jupiter keeps 20% of the fee.
75
+ * @see https://developers.jup.ag/docs/swap/fees
76
+ */
77
+ export const JUPITER_REFERRAL_FEE_BPS_RANGE = { min: 50, max: 255 };
78
+ /**
79
+ * Returns `bps` when Jupiter Ultra accepts it as a referral fee: a whole number of bps within
80
+ * `JUPITER_REFERRAL_FEE_BPS_RANGE`. Throws a `RangeError` otherwise: with such a fee Jupiter
81
+ * refuses every swap, so it must never reach the plugin.
82
+ */
83
+ export function assertJupiterReferralFeeBps(bps) {
84
+ const { min, max } = JUPITER_REFERRAL_FEE_BPS_RANGE;
85
+ if (!Number.isInteger(bps) || bps < min || bps > max) {
86
+ throw new RangeError(`Jupiter referral fee must be a whole number of bps between ${min} and ${max} ` +
87
+ `(Jupiter Ultra refuses any other value), got ${String(bps)}`);
88
+ }
89
+ return bps;
90
+ }