@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 CHANGED
@@ -3,13 +3,236 @@
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.1.0 — unreleased
6
+ ## 0.3.0 — 2026-10-17
7
7
 
8
- First version meant for publication on npm as `@forgedevv/core` (with `@forgedevv/shared`), through
9
- `node scripts/publish-core.mjs --publish`. Not published yet.
8
+ Changes after 0.2.0. Publish `@forgedevv/shared` 0.3.0 with it (new kill switch `revenue_paused` in `FlagKey`).
10
9
 
11
- - `listCoinsOfCreator` (creator pools through SDK `getPoolsByCreator`), `DBC_POOL_CREATOR_OFFSET`, `DBC_POOL_CONFIG_OFFSET`.
10
+ ### Changed (Jupiter fixes)
11
+
12
+ - **Jupiter referral fee.** `FEES.jupiterIntegratorFeeBps` (re-exported from `@forge/shared`) is
13
+ 50 bps, was 30: the Jupiter Plugin passes `referralFee` to Ultra `/order`, which refuses any
14
+ value outside 50..255 bps, so every swap of a graduated coin failed. Jupiter keeps 20% of it.
15
+ `FEES.platformFeeBps` (30 bps, `PLATFORM_FEE_BPS` on swaps built by core) is unchanged.
16
+ - **Jupiter referral claims.** `buildJupiterReferralClaimTx` refuses a referral account of another
17
+ project than Jupiter's Ultra project (`project_mismatch`; the new `expectedProject` parameter
18
+ overrides the default `JUPITER_ULTRA_REFERRAL_PROJECT`). A funded v2 collection account is only
19
+ claimed for a named referral account at the PDA `["referral", project, name]`, the only kind
20
+ `claim_v2` accepts: `referral_account_unnamed` or `referral_account_not_pda` otherwise (new
21
+ `JupiterReferralErrorCode` values; the whole build is refused). With an empty v2 account, an
22
+ unnamed account still claims its v1 collection account. Devnet is unchanged: any cluster but
23
+ mainnet-beta is still refused (`wrong_cluster`) before these reads.
24
+
25
+ ### Added (Jupiter fixes)
26
+
27
+ - `JUPITER_REFERRAL_FEE_BPS_RANGE` (`{ min: 50, max: 255 }`) and
28
+ `assertJupiterReferralFeeBps(bps)` (constants.ts): returns a whole number of bps within the range
29
+ Jupiter Ultra accepts, throws a `RangeError` otherwise. The launchpad template runs the plugin
30
+ fee through it and fails closed outside it.
31
+ - `JUPITER_ULTRA_REFERRAL_PROJECT` (`DkiqsTrw1u1bYFumumC7sCG2S8K25qc2vemJFHyW2wJc`) and
32
+ `deriveJupiterReferralAccount(project, name, programId?)` (jupiter-referral.ts).
33
+
34
+ ### Added (revenue wallet referral accounts setup)
35
+
36
+ Pure builders of the two accounts the revenue wallet must hold (docs/MAINNET.md section 5), for
37
+ the signer's one-off `--setup-revenue-referrals` command. Only the payer signs: neither the
38
+ revenue wallet nor the new accounts do.
39
+
40
+ - **`meteora-referral.ts`** (re-exported by the index): `deriveMeteoraReferralAccount(payer,
41
+ seed?)` (`createWithSeed(payer, seed, SPL Token)`, default seed
42
+ `METEORA_REFERRAL_ACCOUNT_SEED` = `forge-meteora-referral`), `buildCreateMeteoraReferralAccountIxs`
43
+ / `buildCreateMeteoraReferralAccountTx({ payer, owner, seed?, rentLamports, blockhash,
44
+ lastValidBlockHeight, priorityFee? })`: System `createAccountWithSeed` (from = base = payer,
45
+ the rent of 165 bytes, SPL Token owned) then SPL Token `InitializeAccount3` (wrapped SOL,
46
+ owner = the revenue wallet, no owner signature), the compute budget pair first when the quote
47
+ has a price (`METEORA_REFERRAL_SETUP_COMPUTE_UNITS` = 30,000); `inspectMeteoraReferralAccount`
48
+ (pure: `absent`, `ready`, or `mismatch` with a reason: not a token account, wrong mint or owner,
49
+ the owner's associated account, frozen, delegated, closable by someone else) and
50
+ `readMeteoraReferralAccount` (any cluster); `MeteoraReferralError` (`invalid_params`).
51
+ - **Jupiter referral setup** (jupiter-referral.ts), verified against the program source
52
+ (`initialize_referral_account_with_name.rs`, `lib.rs`, `claim_v2.rs`) and the SDK 0.3.0 IDL:
53
+ `JUPITER_INITIALIZE_REFERRAL_ACCOUNT_WITH_NAME_DISCRIMINATOR`, `JUPITER_REFERRAL_ACCOUNT_SIZE`
54
+ (274), `JUPITER_REFERRAL_NAME_MAX_CHARS` (19: the program requires fewer than 20 characters)
55
+ and `JUPITER_REFERRAL_NAME_MAX_BYTES` (32: a PDA seed), `assertJupiterReferralName` (also
56
+ refuses whitespace and control characters), `encodeJupiterReferralName` (Borsh),
57
+ `buildJupiterInitializeReferralAccountWithNameIx({ payer, partner, project, referralAccount },
58
+ name)` (the IDL's accounts: payer the only signer, partner read-only and not a signer, the
59
+ referral account the PDA `["referral", project, name]`),
60
+ `buildJupiterReferralTokenAccountV2Ix({ payer, referralAccount, mint, tokenProgram })` (the v2
61
+ collection account `claim_v2` empties is the referral account's associated token account: an
62
+ idempotent creation, what the SDK's `initializeReferralTokenAccountV2` sends),
63
+ `buildJupiterReferralSetupTx({ payer, partner, project?, name, createReferralAccount,
64
+ tokenAccounts, blockhash, lastValidBlockHeight, priorityFee? })` (Ultra project by default,
65
+ `JUPITER_REFERRAL_SETUP_COMPUTE_UNITS` = 150,000), `inspectJupiterReferralAccount` /
66
+ `inspectJupiterReferralTokenAccountV2` (pure statuses with mismatch reasons: partner, project,
67
+ name, owner, mint) and `readJupiterReferralSetup({ connection, partner, name, mints })`
68
+ (mainnet-beta only, `wrong_cluster` before any read; reads the project, the mints' token
69
+ programs, the referral account and its collection accounts).
70
+
71
+ ### Changed (revenue wallet, docs/DECISIONS.md D34)
72
+
73
+ - **New address `FORGE_REVENUE_WALLET`** (`FORGE_ADDRESS_NAMES`, `getRevenueWallet`): the
74
+ signer-held wallet where the non-native trading revenue lands before the signer converts it to
75
+ SOL and forwards it to the multisig vault. It replaces the vault as the holder of the Meteora
76
+ referral account and as the partner of the Jupiter referral account; the platform fee goes to
77
+ it too (`FORGE_PLATFORM_FEE_WALLET` must be that wallet).
78
+ - **Swaps.** `swapFeeAddresses` returns `revenueWallet` (was `multisigVault`), read from
79
+ `FORGE_REVENUE_WALLET` (optional, as before): when set, the Meteora referral token account must
80
+ be held by it (`REFERRAL_ACCOUNT_INVALID`, `assertReferralAccountReady(..., expectedOwner?)`)
81
+ and `FORGE_PLATFORM_FEE_WALLET` must be it (`PLATFORM_FEE_WALLET_MISMATCH`, checked before any
82
+ network call by `buildBuyTx`, `buildSellTx` and `assertSwapFeeAccountsReady`). Client sites
83
+ receive `NEXT_PUBLIC_FORGE_REVENUE_WALLET` instead of `NEXT_PUBLIC_FORGE_MULTISIG_VAULT`.
84
+ - `buildJupiterReferralClaimTx` is unchanged: `expectedPartner` is now the revenue wallet.
85
+
86
+ ### Added (priority fees)
87
+
88
+ - **`priority-fee.ts`** (browser-safe, re-exported by the index): `computePriorityFee` (pure: the
89
+ nearest-rank `percentile` of `getRecentPrioritizationFees`, at least `floorMicroLamports`, at
90
+ most `maxMicroLamports` per CU and `maxLamports` for the whole transaction; an empty list or a
91
+ failed read prices at the floor, reported in `source`), `estimatePriorityFee` (reads the fees
92
+ for the transaction's writable accounts, at most `MAX_PRIORITY_FEE_ACCOUNTS` = 32),
93
+ `readRecentPriorityFees`, `resolvePriorityFeePolicy`, `feePercentile`, `maxPriceForBudget`,
94
+ `maxPriorityFeeLamports` (the worst case of a setting, to show before building),
95
+ `fixedPriorityFee`, `priorityFeeInstructions` (unit limit then price; `priceOnly`),
96
+ `writableAccountsOf`, and `checkComputeBudgetInstructions` / `ComputeBudgetCheckError` for
97
+ transaction policies (no account, `SetComputeUnitLimit` / `SetComputeUnitPrice` only, at most
98
+ one of each, bounded values and fee; without a limit the fee is counted for 1.4M CU).
99
+ `DEFAULT_PRIORITY_FEE_POLICY`: 75th percentile, floor 10,000 micro-lamports per CU, at most
100
+ 1,000,000 per CU and 500,000 lamports (0.0005 SOL) per transaction; hard ceilings
101
+ `MAX_PRIORITY_FEE_MICRO_LAMPORTS` (10 lamports per CU) and `MAX_PRIORITY_FEE_LAMPORTS`
102
+ (0.01 SOL). Types `PriorityFeeSetting`, `PriorityFeePolicy`, `PriorityFeeQuote`,
103
+ `PriorityFeeSource`, `RecentPrioritizationFee`.
104
+ - **Builders accept an optional `priority` setting** (exclusive with their fixed price, if any),
105
+ which puts `setComputeUnitLimit` then `setComputeUnitPrice` first (after the nonce advance):
106
+ `buildBuyTx` / `buildSellTx` (`SwapBuildOptions.priority`; the pure `buildSwapTransaction`
107
+ refuses it), `buildPaymentTx` (`PaymentTxOptions.priority`, limit `PAYMENT_COMPUTE_UNITS` =
108
+ 50,000), `buildLaunchCoinTx` and `buildUserCoinLaunchTx`, `buildCreateConfigTx`, the creator,
109
+ partner and pool creation fee claims (`ClaimOptions.priority`, limits `CLAIM_COMPUTE_UNITS` =
110
+ 200,000 and `POOL_CREATION_FEE_CLAIM_COMPUTE_UNITS` = 100,000), the DAMM v2 position claim
111
+ (`positionClaimComputeUnits`) and `buildJupiterReferralClaimTx`
112
+ (`JUPITER_REFERRAL_CLAIM_COMPUTE_UNITS` = 300,000). Their results report the quote
113
+ (`priorityFee`), and the claim costs count the fee. `CREATE_NONCE_ACCOUNT_COMPUTE_UNITS`
114
+ (10,000) names the limit of the nonce account builders.
115
+ - **Launch sizes.** The launchpad coin launch keeps the 148-byte metadata budget: with a priority
116
+ fee it carries the unit limit and the price up to 136 bytes of metadata, the price alone up to
117
+ 144 (priced for `MAX_TRANSACTION_COMPUTE_UNITS`), and no price beyond
118
+ (`LaunchCoinSummary.priorityFeePlacement`: `limit_and_price`, `price_only`,
119
+ `omitted_for_size`). A priced swap or user coin launch is 12 bytes larger.
120
+
121
+ ### Changed (priority fees)
122
+
123
+ - `BuiltSwapTransaction.priorityFee`, `PaymentTxResult.priorityFee`, `CreateConfigTx.priorityFee`,
124
+ `ClaimSummary.priorityFee`, `PoolCreationFeeClaimSummary.priorityFee`,
125
+ `PositionClaimSummary.priorityFee`, `JupiterReferralClaimTx.priorityFee` and
126
+ `LaunchCoinSummary` / `UserCoinLaunchSummary` `.priorityFee` and `.priorityFeePlacement` are new
127
+ required fields: code that builds these objects itself (test fakes) must set them. Without a
128
+ `priority` setting every transaction is byte for byte what 0.2.0 built.
129
+ - `ClaimCosts.networkFeeLamports` and `PositionClaimCosts.networkFeeLamports` include the
130
+ priority fee (0 without a setting).
131
+ - Consumers: the launchpad template prices its trades, coin creations and creator fee claims with
132
+ the defaults, so client sites need this version, and the FORGE RPC relay must serve
133
+ `getRecentPrioritizationFees` (apps/web, same release).
134
+
135
+ ### Added (on-chain fallback of the coin data)
136
+
137
+ - `listPoolStatesOfConfig(connection, config, options?)` (reads.ts): the decoded pools of a config
138
+ with the config state (`DbcPoolState[]`, sorted by pool address), from the SDK's
139
+ `getPoolsByConfig`; a pool whose decoded config differs is dropped. `listCoinsOfConfig` now
140
+ summarizes its result (unchanged output). The launchpad template lists its coins with it when
141
+ the Jupiter data API is unavailable (and always on devnet).
142
+ - `poolSpotPrice(sqrtPrice, baseDecimals, quoteDecimals = 9)` (reads.ts): display price of one
143
+ whole coin in whole quote tokens from the pool's Q64.64 `sqrtPrice` (same value as the SDK's
144
+ `getPriceFromSqrtPrice`, as a float).
145
+
146
+ ## 0.2.0 — 2026-10-17
147
+
148
+ Everything below was added after 0.1.0 and published on npm on 2026-10-17 with `@forgedevv/shared` 0.2.0.
149
+
150
+ ### Changed (security review)
151
+
152
+ - **On-chain configs compared field by field.** New `onchainConfigMismatches(config, kind,
153
+ { spec?, owner?, poolCreationFeeLamports? })` (config.ts) compares a decoded config with the
154
+ parameters core builds for it: accounts, token, base fee (cliff, periods, period, reduction,
155
+ mode), dynamic fee, creator share, pool creation fee, first swap at the minimum fee, activation,
156
+ liquidity split and vesting, migration option / fee option / threshold, migration fee
157
+ percentages, migrated pool fee, locked vesting, token supply, start price and curve. Without a
158
+ spec, the spec fields are read from the config and must be values a FORGE spec can hold.
159
+ - `assertLaunchCoinConfig(config, owner, spec?)`: runs it (optional `spec`, recommended).
160
+ `BuildLaunchCoinTxParams.spec?` passes it. A coin config with a pool creation fee is now
161
+ `CONFIG_MISMATCH` (the current builder sets none).
162
+ - `assertUserCoinConfig(config, expected?)`: runs it against the launchpad config;
163
+ `BuildUserCoinLaunchTxParams.expectedConfig?` passes the expectation.
164
+ - **validate.ts pins every value-moving field to FORGE's settings**: no migration fee
165
+ (`MIGRATION_FEE_NOT_ALLOWED`), no locked vesting (`LOCKED_VESTING_NOT_ALLOWED`), the fixed supply
166
+ of `FORGE_TOKEN_SETTINGS` (`TOKEN_SUPPLY_MISMATCH`), FORGE's curve and start price
167
+ (`CURVE_MISMATCH`, they decide the leftover), SPL token with 6 decimals, timestamp activation,
168
+ and the anti-sniper schedule exactly `ANTI_SNIPER_FEE_SCHEDULE` (a flat linear fee when
169
+ `spec.antiSniper` is off): an exponential schedule, another starting fee or one 300 s period at
170
+ 99% are refused.
171
+ - The fixed settings moved to `settings.ts` (re-exported by config.ts, unchanged values), with
172
+ `FORGE_TOKEN_SUPPLY_RAW`, `forgeFeeSchedulerParam`, `forgeBaseFee`, `forgeReferenceCurve`.
173
+ `curveParamsFor`, `creatorTradingFeePercentageFor` and `poolCreationFeeSolFor` accept just the
174
+ spec fields they read (`ConfigSpecFields`); a full `LaunchpadSpec` still fits.
175
+ - **Platform fee on a user coin's first buy.** `buildUserCoinLaunchTx` adds ONE SystemProgram
176
+ transfer of `platformFeeLamports(firstBuyLamports)` (30 bps, rounded up) to
177
+ `FORGE_PLATFORM_FEE_WALLET` (read from `env`), as the last instruction; `chargePlatformFee:
178
+ false` turns it off. The wallet must be rent-exempt (`PLATFORM_FEE_WALLET_NOT_READY`). The
179
+ summary gains `platformFeeLamports` and `platformFeeWallet`; `LaunchCoinCosts.platformFeeLamports`
180
+ (optional) is set and counted in `ownerTotalLamports`.
181
+ - **Swaps.** `MAX_SWAP_SLIPPAGE_BPS` is 2,500 (25%, was 50%). `buildBuyTx`, `buildSellTx`,
182
+ `quoteSwap`, `fetchSwapContext` and `fetchSwapPoolState` take an optional `allowedConfigs`: a
183
+ pool whose on-chain config is not listed is refused (`POOL_NOT_ALLOWED`) before its config is
184
+ read. The builders now require the platform fee wallet to be exactly `FORGE_PLATFORM_FEE_WALLET`
185
+ and a system account (`PLATFORM_FEE_WALLET_MISMATCH`), and, when the environment has
186
+ `FORGE_MULTISIG_VAULT`, the Meteora referral token account to be held by it
187
+ (`REFERRAL_ACCOUNT_INVALID`). `buildSwapTransaction(ctx, request, expected?)`,
188
+ `assertPlatformFeeWalletReady(..., expectedWallet?)`, `assertReferralAccountReady(...,
189
+ expectedOwner?)`, new `assertPoolConfigAllowed`, `readOptionalAddress` (addresses.ts);
190
+ `swapFeeAddresses` also returns `multisigVault`.
12
191
  - `checkPaymentTransaction` / `verifyPayment`: `transferLamports` on `amount_mismatch` when the transfer amount differs.
192
+ - Jupiter referral fee claim (`jupiter-referral.ts`, mainnet-beta only): `buildJupiterReferralClaimTx`
193
+ reads the referral account, its project and the two collection accounts of a mint (classic
194
+ `referral_ata` PDA and the referral account's associated token account) and builds the
195
+ permissionless `claim` / `claim_v2` instructions to the referral account's partner (the FORGE
196
+ multisig vault, asserted with `expectedPartner`). Raw instructions from the referral program IDL
197
+ (no Anchor dependency), with the pure pieces exported (`decodeJupiterReferralAccount`,
198
+ `decodeJupiterProject`, `deriveJupiterReferralTokenAccount[V2]`, `buildJupiterReferralClaimIx`,
199
+ `buildJupiterCreateAdminTokenAccountIx`). Any cluster but mainnet-beta is refused
200
+ (`JupiterReferralError` `wrong_cluster`). The summary also carries the project's `admin` and
201
+ `projectAuthority`, so a caller can pin every account of the built claim.
202
+ - Squads accounts of the buyback read in addresses.ts (PLANEXECUTE rule 7): `FORGE_SQUADS_MULTISIG`
203
+ and `FORGE_SQUADS_SPENDING_LIMIT` join `FORGE_ADDRESS_NAMES` as optional names;
204
+ `readSquadsAddresses(env)` returns them with `FORGE_SQUADS_VAULT_INDEX` (default 0) or `null`
205
+ when both are absent, and refuses half a configuration, an index without them, an index outside
206
+ 0..255 or a spending limit equal to the multisig (`ForgeSquadsConfigError`). Deriving the vault
207
+ needs the Squads program id: the signer keeps that check.
208
+
209
+ ### Added (DAMM v2 position fee claims)
210
+
211
+ - `position-claims.ts`: after a launchpad coin migrates, the FORGE creator wallet keeps a
212
+ permanently locked DAMM v2 position that earns the migrated pool's trading fees.
213
+ `buildCreatorPositionFeeClaimTx` builds the unsigned `claim_position_fee` of the creator's
214
+ position(s) to a receiver (`buildForgeCreatorPositionFeeClaimTx`: the multisig vault), creator
215
+ signing, optional fee payer: coin fees to the receiver's associated account, SOL fees to the
216
+ creator's temporary WSOL account closed to the receiver (fees plus rent as lamports). The
217
+ transaction never removes liquidity nor closes, locks or transfers the position or its NFT.
218
+ Summary, costs (`PositionClaimCosts`), skip reasons (`no_position`, `nothing_to_claim`,
219
+ `not_worthwhile`), `isPositionClaimWorthwhile`. Pure helpers: `migratedDammV2Pool` (the DAMM v2
220
+ pool of a migrated DBC pool, derived like the SDK migration), `positionPendingFees`,
221
+ `summarizePosition`, `sumPendingFees`; reads: `findCreatorDammV2Positions` (the creator's
222
+ positions, NFT ownership re-checked), `getDammV2PoolState`. Constants for policies:
223
+ `DAMM_V2_PROGRAM_ID`, `CLAIM_POSITION_FEE_DISCRIMINATOR`, `CLAIM_POSITION_FEE_ACCOUNTS`.
224
+ New `ClaimError` codes: `not_migrated`, `damm_pool_not_found`, `damm_pool_mismatch`,
225
+ `position_not_found`, `position_mismatch`, `position_not_owned`.
226
+ - Dependency: `@meteora-ag/cp-amm-sdk` 1.5.1 (exact pin, like the DBC SDK).
227
+ - Fixtures: `__fixtures__/cp-amm-devnet-accounts.json`, the devnet test 2 migration captured
228
+ read-only (`scripts/devnet-tests` `fixtures:cp-amm`).
229
+
230
+ ## 0.1.0 — 2026-10-14
231
+
232
+ First version published on npm as `@forgedevv/core` (with `@forgedevv/shared`), through
233
+ `node scripts/publish-core.mjs --publish`.
234
+
235
+ - `listCoinsOfCreator` (creator pools through SDK `getPoolsByCreator`), `DBC_POOL_CREATOR_OFFSET`, `DBC_POOL_CONFIG_OFFSET`.
13
236
 
14
237
  ### Changed (wave 5 review)
15
238
 
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # @forgedevv/core
2
2
 
3
3
  The FORGE transaction core: it builds the Meteora Dynamic Bonding Curve configs, the launchpad coin
4
- launch, buys and sells with the FORGE platform fee and referral, creator fee claims, payment checks
5
- and the read helpers used by FORGE launchpad sites. Every function that builds a transaction
6
- returns it **unsigned**: the user signs in their wallet.
4
+ launch, buys and sells with the FORGE platform fee and referral, creator fee claims (bonding curve
5
+ share, and the DAMM v2 position fees of a migrated pool), payment checks and the read helpers used
6
+ by FORGE launchpad sites. Every function that builds a transaction returns it **unsigned**: the
7
+ user signs in their wallet.
7
8
 
8
9
  Inside the FORGE monorepo this package is `@forge/core`. It is published on the public npm registry
9
10
  as `@forgedevv/core` (with its contracts package `@forgedevv/shared`), and client projects install
@@ -9,17 +9,26 @@
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"];
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
- /** Addresses that may be absent. `FORGE_MINT` is absent before the $FORGE launch. */
22
- export declare const OPTIONAL_FORGE_ADDRESS_NAMES: readonly ["FORGE_MINT"];
27
+ /**
28
+ * Addresses that may be absent. `FORGE_MINT` is absent before the $FORGE launch; the Squads
29
+ * accounts are absent while the buyback stays in simulation (see `readSquadsAddresses`).
30
+ */
31
+ export declare const OPTIONAL_FORGE_ADDRESS_NAMES: readonly ["FORGE_MINT", "FORGE_SQUADS_MULTISIG", "FORGE_SQUADS_SPENDING_LIMIT"];
23
32
  export type OptionalForgeAddressName = (typeof OPTIONAL_FORGE_ADDRESS_NAMES)[number];
24
33
  /** Environment object to read from (defaults to `process.env`; injectable for tests). */
25
34
  export type AddressEnv = Readonly<Record<string, string | undefined>>;
@@ -41,15 +50,72 @@ export declare class ForgeAddressError extends Error {
41
50
  * @throws ForgeAddressError listing the missing and invalid names (never the values).
42
51
  */
43
52
  export declare function requireAddresses<N extends ForgeAddressName>(names: readonly N[], env?: AddressEnv): ForgeAddresses<N>;
44
- /** Squads multisig vault: receives the creator fees, the Meteora referral and the cashbox sweeps. */
53
+ /**
54
+ * One FORGE address when it is set, `null` when it is absent or empty. A value that is set but is
55
+ * not a valid public key is refused, exactly as `requireAddresses` does. For checks that run
56
+ * wherever the address is known and are skipped elsewhere (e.g. the owner of the Meteora referral
57
+ * account, `FORGE_REVENUE_WALLET`, in a browser that was not given it).
58
+ *
59
+ * @throws ForgeAddressError (invalid value; names only).
60
+ */
61
+ export declare function readOptionalAddress(name: ForgeAddressName, env?: AddressEnv): PublicKey | null;
62
+ /**
63
+ * Squads multisig vault: receives the creator fees, the cashbox sweeps and the revenue wallet's
64
+ * forwards (the converted trading revenue).
65
+ */
45
66
  export declare function getMultisigVault(env?: AddressEnv): PublicKey;
46
- /** 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`. */
47
68
  export declare function getMeteoraReferralAccount(env?: AddressEnv): PublicKey;
48
- /** 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
+ */
49
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;
50
81
  /** Jupiter referral account (integrator fee after graduation). */
51
82
  export declare function getJupiterReferralAccount(env?: AddressEnv): PublicKey;
52
83
  /** Cashbox wallet (held by the signer) receiving client payments. */
53
84
  export declare function getCashboxWallet(env?: AddressEnv): PublicKey;
54
85
  /** $FORGE mint, or `null` before the $FORGE launch (token-gating off, buyback simulated). */
55
86
  export declare function getForgeMint(env?: AddressEnv): PublicKey | null;
87
+ /** Squads v4 accounts the buyback withdraws through, read from the environment. */
88
+ export interface SquadsAddresses {
89
+ /** FORGE_SQUADS_MULTISIG: the multisig account. */
90
+ multisig: PublicKey;
91
+ /** FORGE_SQUADS_SPENDING_LIMIT: the spending limit account of the buyback wallet. */
92
+ spendingLimit: PublicKey;
93
+ /** FORGE_SQUADS_VAULT_INDEX (default 0): the vault index of the treasury vault. */
94
+ vaultIndex: number;
95
+ }
96
+ export type SquadsAddressProblem =
97
+ /** One of the two addresses without the other. */
98
+ 'incomplete'
99
+ /** FORGE_SQUADS_VAULT_INDEX other than the default 0 without the addresses. */
100
+ | 'index_without_multisig'
101
+ /** FORGE_SQUADS_VAULT_INDEX is not an integer between 0 and 255. */
102
+ | 'invalid_index'
103
+ /** The spending limit account is the multisig account. */
104
+ | 'not_distinct';
105
+ /** Thrown by `readSquadsAddresses` on a rule the names alone cannot express. Names only. */
106
+ export declare class ForgeSquadsConfigError extends Error {
107
+ readonly problem: SquadsAddressProblem;
108
+ constructor(problem: SquadsAddressProblem, message: string);
109
+ }
110
+ /**
111
+ * FORGE_SQUADS_MULTISIG, FORGE_SQUADS_SPENDING_LIMIT and FORGE_SQUADS_VAULT_INDEX. Returns `null`
112
+ * when both addresses are absent (no withdrawer: the buyback stays in simulation). Rules: the
113
+ * two addresses are set together or not at all; an index other than the default `0` needs them
114
+ * (a lone `0` is the default spelled out, not a configuration); the index is an integer between
115
+ * 0 and 255 (default 0); the spending limit is a distinct account. Deriving the vault from the
116
+ * multisig and the index, and comparing it with FORGE_MULTISIG_VAULT, needs the Squads program
117
+ * id (pinned SDK): the caller (the signer) does it.
118
+ *
119
+ * @throws ForgeAddressError (invalid address, names only), ForgeSquadsConfigError (other rules).
120
+ */
121
+ export declare function readSquadsAddresses(env?: AddressEnv): SquadsAddresses | null;
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,13 +26,21 @@ 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',
33
+ 'FORGE_SQUADS_MULTISIG',
34
+ 'FORGE_SQUADS_SPENDING_LIMIT',
26
35
  ];
27
- /** Addresses that may be absent. `FORGE_MINT` is absent before the $FORGE launch. */
36
+ /**
37
+ * Addresses that may be absent. `FORGE_MINT` is absent before the $FORGE launch; the Squads
38
+ * accounts are absent while the buyback stays in simulation (see `readSquadsAddresses`).
39
+ */
28
40
  export const OPTIONAL_FORGE_ADDRESS_NAMES = [
29
41
  'FORGE_MINT',
42
+ 'FORGE_SQUADS_MULTISIG',
43
+ 'FORGE_SQUADS_SPENDING_LIMIT',
30
44
  ];
31
45
  /** Thrown when required addresses are missing or invalid. Holds names only, never values. */
32
46
  export class ForgeAddressError extends Error {
@@ -101,18 +115,47 @@ export function requireAddresses(names, env = defaultEnv()) {
101
115
  throw new ForgeAddressError(missing, invalid);
102
116
  return result;
103
117
  }
104
- /** Squads multisig vault: receives the creator fees, the Meteora referral and the cashbox sweeps. */
118
+ /**
119
+ * One FORGE address when it is set, `null` when it is absent or empty. A value that is set but is
120
+ * not a valid public key is refused, exactly as `requireAddresses` does. For checks that run
121
+ * wherever the address is known and are skipped elsewhere (e.g. the owner of the Meteora referral
122
+ * account, `FORGE_REVENUE_WALLET`, in a browser that was not given it).
123
+ *
124
+ * @throws ForgeAddressError (invalid value; names only).
125
+ */
126
+ export function readOptionalAddress(name, env = defaultEnv()) {
127
+ const value = env[name];
128
+ if (value === undefined || value === '')
129
+ return null;
130
+ return requireAddresses([name], env)[name];
131
+ }
132
+ /**
133
+ * Squads multisig vault: receives the creator fees, the cashbox sweeps and the revenue wallet's
134
+ * forwards (the converted trading revenue).
135
+ */
105
136
  export function getMultisigVault(env) {
106
137
  return requireAddresses(['FORGE_MULTISIG_VAULT'], env).FORGE_MULTISIG_VAULT;
107
138
  }
108
- /** 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`. */
109
140
  export function getMeteoraReferralAccount(env) {
110
141
  return requireAddresses(['FORGE_METEORA_REFERRAL_ACCOUNT'], env).FORGE_METEORA_REFERRAL_ACCOUNT;
111
142
  }
112
- /** 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
+ */
113
148
  export function getPlatformFeeWallet(env) {
114
149
  return requireAddresses(['FORGE_PLATFORM_FEE_WALLET'], env).FORGE_PLATFORM_FEE_WALLET;
115
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
+ }
116
159
  /** Jupiter referral account (integrator fee after graduation). */
117
160
  export function getJupiterReferralAccount(env) {
118
161
  return requireAddresses(['FORGE_JUPITER_REFERRAL_ACCOUNT'], env).FORGE_JUPITER_REFERRAL_ACCOUNT;
@@ -125,3 +168,51 @@ export function getCashboxWallet(env) {
125
168
  export function getForgeMint(env) {
126
169
  return requireAddresses(['FORGE_MINT'], env).FORGE_MINT;
127
170
  }
171
+ /** Thrown by `readSquadsAddresses` on a rule the names alone cannot express. Names only. */
172
+ export class ForgeSquadsConfigError extends Error {
173
+ problem;
174
+ constructor(problem, message) {
175
+ super(message);
176
+ this.name = 'ForgeSquadsConfigError';
177
+ this.problem = problem;
178
+ }
179
+ }
180
+ const VAULT_INDEX_RE = /^(0|[1-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
181
+ /**
182
+ * FORGE_SQUADS_MULTISIG, FORGE_SQUADS_SPENDING_LIMIT and FORGE_SQUADS_VAULT_INDEX. Returns `null`
183
+ * when both addresses are absent (no withdrawer: the buyback stays in simulation). Rules: the
184
+ * two addresses are set together or not at all; an index other than the default `0` needs them
185
+ * (a lone `0` is the default spelled out, not a configuration); the index is an integer between
186
+ * 0 and 255 (default 0); the spending limit is a distinct account. Deriving the vault from the
187
+ * multisig and the index, and comparing it with FORGE_MULTISIG_VAULT, needs the Squads program
188
+ * id (pinned SDK): the caller (the signer) does it.
189
+ *
190
+ * @throws ForgeAddressError (invalid address, names only), ForgeSquadsConfigError (other rules).
191
+ */
192
+ export function readSquadsAddresses(env = defaultEnv()) {
193
+ const multisigRaw = env.FORGE_SQUADS_MULTISIG ?? '';
194
+ const limitRaw = env.FORGE_SQUADS_SPENDING_LIMIT ?? '';
195
+ const indexRaw = env.FORGE_SQUADS_VAULT_INDEX ?? '';
196
+ if (multisigRaw === '' && limitRaw === '') {
197
+ if (indexRaw !== '' && indexRaw !== '0') {
198
+ throw new ForgeSquadsConfigError('index_without_multisig', 'FORGE_SQUADS_VAULT_INDEX needs FORGE_SQUADS_MULTISIG');
199
+ }
200
+ return null;
201
+ }
202
+ if (multisigRaw === '' || limitRaw === '') {
203
+ throw new ForgeSquadsConfigError('incomplete', 'FORGE_SQUADS_MULTISIG and FORGE_SQUADS_SPENDING_LIMIT must be set together');
204
+ }
205
+ const multisig = readOptionalAddress('FORGE_SQUADS_MULTISIG', env);
206
+ const spendingLimit = readOptionalAddress('FORGE_SQUADS_SPENDING_LIMIT', env);
207
+ let vaultIndex = 0;
208
+ if (indexRaw !== '') {
209
+ if (!VAULT_INDEX_RE.test(indexRaw)) {
210
+ throw new ForgeSquadsConfigError('invalid_index', 'FORGE_SQUADS_VAULT_INDEX must be an integer between 0 and 255');
211
+ }
212
+ vaultIndex = Number(indexRaw);
213
+ }
214
+ if (spendingLimit.equals(multisig)) {
215
+ throw new ForgeSquadsConfigError('not_distinct', 'FORGE_SQUADS_SPENDING_LIMIT must be a distinct account');
216
+ }
217
+ return { multisig, spendingLimit, vaultIndex };
218
+ }
package/dist/claims.d.ts CHANGED
@@ -1,10 +1,19 @@
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 RetryOptions, type TokenAmounts } from './reads.js';
4
- /** Base fee per signature (lamports); claims set no priority fee. */
3
+ import { type PriorityFeeQuote, type PriorityFeeSetting } from './priority-fee.js';
4
+ import { type DbcPoolState, type RetryOptions, type TokenAmounts } from './reads.js';
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
- 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';
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 {
9
18
  readonly code: ClaimErrorCode;
10
19
  constructor(code: ClaimErrorCode, message: string);
@@ -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,
@@ -43,6 +52,12 @@ export interface ClaimSummary {
43
52
  */
44
53
  receiverQuoteLamports: bigint;
45
54
  quoteIsExact: boolean;
55
+ /**
56
+ * WSOL the claimer's temporary account already held when the transaction was built and that
57
+ * the close hands to the receiver (counted in `receiverQuoteLamports`). Zero when the account
58
+ * is empty or new, and when the close returns it to a claimer paying its own claim.
59
+ */
60
+ existingWsolLamports: bigint;
46
61
  /** Zero costs when there is no transaction. */
47
62
  costs: ClaimCosts;
48
63
  /**
@@ -50,6 +65,8 @@ export interface ClaimSummary {
50
65
  * costs are paid (negative when it costs more than it collects). Base tokens are not valued.
51
66
  */
52
67
  netLamports: bigint;
68
+ /** The priority fee of the transaction (price, fee, cap), or null without one. */
69
+ priorityFee: PriorityFeeQuote | null;
53
70
  }
54
71
  export type ClaimSkipReason = 'nothing_to_claim' | 'not_worthwhile';
55
72
  export interface ClaimTxResult {
@@ -69,7 +86,21 @@ export interface ClaimOptions {
69
86
  * than this many lamports (see `isClaimWorthwhile`). Default: never skip a non-empty claim.
70
87
  */
71
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;
72
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
+ }>;
73
104
  export interface CreatorClaimParams {
74
105
  pool: PublicKey;
75
106
  /** Pool creator (must match the pool state); signs the claim. */
@@ -100,6 +131,10 @@ export declare function minimumWorthwhileClaimLamports(costs: ClaimCosts, minNet
100
131
  * callers skip claims below it. Base tokens are not valued (FORGE configs collect SOL fees only).
101
132
  */
102
133
  export declare function isClaimWorthwhile(summary: ClaimSummary, minNetLamports?: bigint): boolean;
134
+ /** @throws ClaimError `unsupported_quote_mint` unless the pool is quoted in SOL. */
135
+ export declare function assertSolQuotedPool(state: DbcPoolState): void;
136
+ /** @throws ClaimError `invalid_receiver` for anything but a usable wallet address. */
137
+ export declare function assertClaimReceiver(receiver: PublicKey): void;
103
138
  /**
104
139
  * Builds the creator claim of `pool` to `receiver` (see the module comment for the transaction).
105
140
  * @throws ClaimError when `creator` is not the pool creator, the pool is not SOL-quoted, the
@@ -153,10 +188,12 @@ export interface PoolCreationFeeClaimSummary {
153
188
  alreadyClaimed: boolean;
154
189
  /** SOL the receiver gets from the transaction (0 when there is none), lamports. */
155
190
  claimableLamports: bigint;
156
- /** 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). */
157
192
  networkFeeLamports: bigint;
158
193
  /** `claimableLamports - networkFeeLamports` (0 when there is no transaction). */
159
194
  netLamports: bigint;
195
+ /** The priority fee of the transaction (price, fee, cap), or null without one. */
196
+ priorityFee: PriorityFeeQuote | null;
160
197
  }
161
198
  export type PoolCreationFeeSkipReason = 'no_pool_creation_fee' | 'already_claimed';
162
199
  export interface PoolCreationFeeClaimTxResult {