@forgedevv/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -5
- package/README.md +4 -3
- package/dist/addresses.d.ts +50 -3
- package/dist/addresses.js +70 -1
- package/dist/claims.d.ts +12 -2
- package/dist/claims.js +29 -11
- package/dist/config.d.ts +40 -37
- package/dist/config.js +265 -47
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -1
- package/dist/jupiter-referral.d.ts +119 -0
- package/dist/jupiter-referral.js +295 -0
- package/dist/launch-metadata.d.ts +3 -1
- package/dist/pool.d.ts +68 -17
- package/dist/pool.js +112 -39
- package/dist/position-claims.d.ts +207 -0
- package/dist/position-claims.js +440 -0
- package/dist/settings.d.ts +73 -0
- package/dist/settings.js +119 -0
- package/dist/swap.d.ts +70 -17
- package/dist/swap.js +89 -24
- package/dist/validate.d.ts +14 -1
- package/dist/validate.js +86 -6
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,13 +3,96 @@
|
|
|
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.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
## 0.2.0 — unreleased
|
|
7
|
+
|
|
8
|
+
Everything below was added after 0.1.0 was published. Publish `@forgedevv/shared` 0.2.0 with it.
|
|
9
|
+
|
|
10
|
+
### Changed (security review)
|
|
11
|
+
|
|
12
|
+
- **On-chain configs compared field by field.** New `onchainConfigMismatches(config, kind,
|
|
13
|
+
{ spec?, owner?, poolCreationFeeLamports? })` (config.ts) compares a decoded config with the
|
|
14
|
+
parameters core builds for it: accounts, token, base fee (cliff, periods, period, reduction,
|
|
15
|
+
mode), dynamic fee, creator share, pool creation fee, first swap at the minimum fee, activation,
|
|
16
|
+
liquidity split and vesting, migration option / fee option / threshold, migration fee
|
|
17
|
+
percentages, migrated pool fee, locked vesting, token supply, start price and curve. Without a
|
|
18
|
+
spec, the spec fields are read from the config and must be values a FORGE spec can hold.
|
|
19
|
+
- `assertLaunchCoinConfig(config, owner, spec?)`: runs it (optional `spec`, recommended).
|
|
20
|
+
`BuildLaunchCoinTxParams.spec?` passes it. A coin config with a pool creation fee is now
|
|
21
|
+
`CONFIG_MISMATCH` (the current builder sets none).
|
|
22
|
+
- `assertUserCoinConfig(config, expected?)`: runs it against the launchpad config;
|
|
23
|
+
`BuildUserCoinLaunchTxParams.expectedConfig?` passes the expectation.
|
|
24
|
+
- **validate.ts pins every value-moving field to FORGE's settings**: no migration fee
|
|
25
|
+
(`MIGRATION_FEE_NOT_ALLOWED`), no locked vesting (`LOCKED_VESTING_NOT_ALLOWED`), the fixed supply
|
|
26
|
+
of `FORGE_TOKEN_SETTINGS` (`TOKEN_SUPPLY_MISMATCH`), FORGE's curve and start price
|
|
27
|
+
(`CURVE_MISMATCH`, they decide the leftover), SPL token with 6 decimals, timestamp activation,
|
|
28
|
+
and the anti-sniper schedule exactly `ANTI_SNIPER_FEE_SCHEDULE` (a flat linear fee when
|
|
29
|
+
`spec.antiSniper` is off): an exponential schedule, another starting fee or one 300 s period at
|
|
30
|
+
99% are refused.
|
|
31
|
+
- The fixed settings moved to `settings.ts` (re-exported by config.ts, unchanged values), with
|
|
32
|
+
`FORGE_TOKEN_SUPPLY_RAW`, `forgeFeeSchedulerParam`, `forgeBaseFee`, `forgeReferenceCurve`.
|
|
33
|
+
`curveParamsFor`, `creatorTradingFeePercentageFor` and `poolCreationFeeSolFor` accept just the
|
|
34
|
+
spec fields they read (`ConfigSpecFields`); a full `LaunchpadSpec` still fits.
|
|
35
|
+
- **Platform fee on a user coin's first buy.** `buildUserCoinLaunchTx` adds ONE SystemProgram
|
|
36
|
+
transfer of `platformFeeLamports(firstBuyLamports)` (30 bps, rounded up) to
|
|
37
|
+
`FORGE_PLATFORM_FEE_WALLET` (read from `env`), as the last instruction; `chargePlatformFee:
|
|
38
|
+
false` turns it off. The wallet must be rent-exempt (`PLATFORM_FEE_WALLET_NOT_READY`). The
|
|
39
|
+
summary gains `platformFeeLamports` and `platformFeeWallet`; `LaunchCoinCosts.platformFeeLamports`
|
|
40
|
+
(optional) is set and counted in `ownerTotalLamports`.
|
|
41
|
+
- **Swaps.** `MAX_SWAP_SLIPPAGE_BPS` is 2,500 (25%, was 50%). `buildBuyTx`, `buildSellTx`,
|
|
42
|
+
`quoteSwap`, `fetchSwapContext` and `fetchSwapPoolState` take an optional `allowedConfigs`: a
|
|
43
|
+
pool whose on-chain config is not listed is refused (`POOL_NOT_ALLOWED`) before its config is
|
|
44
|
+
read. The builders now require the platform fee wallet to be exactly `FORGE_PLATFORM_FEE_WALLET`
|
|
45
|
+
and a system account (`PLATFORM_FEE_WALLET_MISMATCH`), and, when the environment has
|
|
46
|
+
`FORGE_MULTISIG_VAULT`, the Meteora referral token account to be held by it
|
|
47
|
+
(`REFERRAL_ACCOUNT_INVALID`). `buildSwapTransaction(ctx, request, expected?)`,
|
|
48
|
+
`assertPlatformFeeWalletReady(..., expectedWallet?)`, `assertReferralAccountReady(...,
|
|
49
|
+
expectedOwner?)`, new `assertPoolConfigAllowed`, `readOptionalAddress` (addresses.ts);
|
|
50
|
+
`swapFeeAddresses` also returns `multisigVault`.
|
|
51
|
+
- `checkPaymentTransaction` / `verifyPayment`: `transferLamports` on `amount_mismatch` when the transfer amount differs.
|
|
52
|
+
- Jupiter referral fee claim (`jupiter-referral.ts`, mainnet-beta only): `buildJupiterReferralClaimTx`
|
|
53
|
+
reads the referral account, its project and the two collection accounts of a mint (classic
|
|
54
|
+
`referral_ata` PDA and the referral account's associated token account) and builds the
|
|
55
|
+
permissionless `claim` / `claim_v2` instructions to the referral account's partner (the FORGE
|
|
56
|
+
multisig vault, asserted with `expectedPartner`). Raw instructions from the referral program IDL
|
|
57
|
+
(no Anchor dependency), with the pure pieces exported (`decodeJupiterReferralAccount`,
|
|
58
|
+
`decodeJupiterProject`, `deriveJupiterReferralTokenAccount[V2]`, `buildJupiterReferralClaimIx`,
|
|
59
|
+
`buildJupiterCreateAdminTokenAccountIx`). Any cluster but mainnet-beta is refused
|
|
60
|
+
(`JupiterReferralError` `wrong_cluster`). The summary also carries the project's `admin` and
|
|
61
|
+
`projectAuthority`, so a caller can pin every account of the built claim.
|
|
62
|
+
- Squads accounts of the buyback read in addresses.ts (PLANEXECUTE rule 7): `FORGE_SQUADS_MULTISIG`
|
|
63
|
+
and `FORGE_SQUADS_SPENDING_LIMIT` join `FORGE_ADDRESS_NAMES` as optional names;
|
|
64
|
+
`readSquadsAddresses(env)` returns them with `FORGE_SQUADS_VAULT_INDEX` (default 0) or `null`
|
|
65
|
+
when both are absent, and refuses half a configuration, an index without them, an index outside
|
|
66
|
+
0..255 or a spending limit equal to the multisig (`ForgeSquadsConfigError`). Deriving the vault
|
|
67
|
+
needs the Squads program id: the signer keeps that check.
|
|
68
|
+
|
|
69
|
+
### Added (DAMM v2 position fee claims)
|
|
70
|
+
|
|
71
|
+
- `position-claims.ts`: after a launchpad coin migrates, the FORGE creator wallet keeps a
|
|
72
|
+
permanently locked DAMM v2 position that earns the migrated pool's trading fees.
|
|
73
|
+
`buildCreatorPositionFeeClaimTx` builds the unsigned `claim_position_fee` of the creator's
|
|
74
|
+
position(s) to a receiver (`buildForgeCreatorPositionFeeClaimTx`: the multisig vault), creator
|
|
75
|
+
signing, optional fee payer: coin fees to the receiver's associated account, SOL fees to the
|
|
76
|
+
creator's temporary WSOL account closed to the receiver (fees plus rent as lamports). The
|
|
77
|
+
transaction never removes liquidity nor closes, locks or transfers the position or its NFT.
|
|
78
|
+
Summary, costs (`PositionClaimCosts`), skip reasons (`no_position`, `nothing_to_claim`,
|
|
79
|
+
`not_worthwhile`), `isPositionClaimWorthwhile`. Pure helpers: `migratedDammV2Pool` (the DAMM v2
|
|
80
|
+
pool of a migrated DBC pool, derived like the SDK migration), `positionPendingFees`,
|
|
81
|
+
`summarizePosition`, `sumPendingFees`; reads: `findCreatorDammV2Positions` (the creator's
|
|
82
|
+
positions, NFT ownership re-checked), `getDammV2PoolState`. Constants for policies:
|
|
83
|
+
`DAMM_V2_PROGRAM_ID`, `CLAIM_POSITION_FEE_DISCRIMINATOR`, `CLAIM_POSITION_FEE_ACCOUNTS`.
|
|
84
|
+
New `ClaimError` codes: `not_migrated`, `damm_pool_not_found`, `damm_pool_mismatch`,
|
|
85
|
+
`position_not_found`, `position_mismatch`, `position_not_owned`.
|
|
86
|
+
- Dependency: `@meteora-ag/cp-amm-sdk` 1.5.1 (exact pin, like the DBC SDK).
|
|
87
|
+
- Fixtures: `__fixtures__/cp-amm-devnet-accounts.json`, the devnet test 2 migration captured
|
|
88
|
+
read-only (`scripts/devnet-tests` `fixtures:cp-amm`).
|
|
89
|
+
|
|
90
|
+
## 0.1.0 — 2026-10-14
|
|
91
|
+
|
|
92
|
+
First version published on npm as `@forgedevv/core` (with `@forgedevv/shared`), through
|
|
93
|
+
`node scripts/publish-core.mjs --publish`.
|
|
10
94
|
|
|
11
95
|
- `listCoinsOfCreator` (creator pools through SDK `getPoolsByCreator`), `DBC_POOL_CREATOR_OFFSET`, `DBC_POOL_CONFIG_OFFSET`.
|
|
12
|
-
- `checkPaymentTransaction` / `verifyPayment`: `transferLamports` on `amount_mismatch` when the transfer amount differs.
|
|
13
96
|
|
|
14
97
|
### Changed (wave 5 review)
|
|
15
98
|
|
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
|
|
5
|
-
and the
|
|
6
|
-
returns it **unsigned**: the
|
|
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
|
package/dist/addresses.d.ts
CHANGED
|
@@ -16,10 +16,13 @@
|
|
|
16
16
|
* Error messages list variable names only, never values.
|
|
17
17
|
*/
|
|
18
18
|
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"];
|
|
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"];
|
|
20
20
|
export type ForgeAddressName = (typeof FORGE_ADDRESS_NAMES)[number];
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Addresses that may be absent. `FORGE_MINT` is absent before the $FORGE launch; the Squads
|
|
23
|
+
* accounts are absent while the buyback stays in simulation (see `readSquadsAddresses`).
|
|
24
|
+
*/
|
|
25
|
+
export declare const OPTIONAL_FORGE_ADDRESS_NAMES: readonly ["FORGE_MINT", "FORGE_SQUADS_MULTISIG", "FORGE_SQUADS_SPENDING_LIMIT"];
|
|
23
26
|
export type OptionalForgeAddressName = (typeof OPTIONAL_FORGE_ADDRESS_NAMES)[number];
|
|
24
27
|
/** Environment object to read from (defaults to `process.env`; injectable for tests). */
|
|
25
28
|
export type AddressEnv = Readonly<Record<string, string | undefined>>;
|
|
@@ -41,6 +44,15 @@ export declare class ForgeAddressError extends Error {
|
|
|
41
44
|
* @throws ForgeAddressError listing the missing and invalid names (never the values).
|
|
42
45
|
*/
|
|
43
46
|
export declare function requireAddresses<N extends ForgeAddressName>(names: readonly N[], env?: AddressEnv): ForgeAddresses<N>;
|
|
47
|
+
/**
|
|
48
|
+
* One FORGE address when it is set, `null` when it is absent or empty. A value that is set but is
|
|
49
|
+
* not a valid public key is refused, exactly as `requireAddresses` does. For checks that run
|
|
50
|
+
* 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).
|
|
52
|
+
*
|
|
53
|
+
* @throws ForgeAddressError (invalid value; names only).
|
|
54
|
+
*/
|
|
55
|
+
export declare function readOptionalAddress(name: ForgeAddressName, env?: AddressEnv): PublicKey | null;
|
|
44
56
|
/** Squads multisig vault: receives the creator fees, the Meteora referral and the cashbox sweeps. */
|
|
45
57
|
export declare function getMultisigVault(env?: AddressEnv): PublicKey;
|
|
46
58
|
/** Meteora referral token account (WSOL, held by the multisig), passed as `referralTokenAccount`. */
|
|
@@ -53,3 +65,38 @@ export declare function getJupiterReferralAccount(env?: AddressEnv): PublicKey;
|
|
|
53
65
|
export declare function getCashboxWallet(env?: AddressEnv): PublicKey;
|
|
54
66
|
/** $FORGE mint, or `null` before the $FORGE launch (token-gating off, buyback simulated). */
|
|
55
67
|
export declare function getForgeMint(env?: AddressEnv): PublicKey | null;
|
|
68
|
+
/** Squads v4 accounts the buyback withdraws through, read from the environment. */
|
|
69
|
+
export interface SquadsAddresses {
|
|
70
|
+
/** FORGE_SQUADS_MULTISIG: the multisig account. */
|
|
71
|
+
multisig: PublicKey;
|
|
72
|
+
/** FORGE_SQUADS_SPENDING_LIMIT: the spending limit account of the buyback wallet. */
|
|
73
|
+
spendingLimit: PublicKey;
|
|
74
|
+
/** FORGE_SQUADS_VAULT_INDEX (default 0): the vault index of the treasury vault. */
|
|
75
|
+
vaultIndex: number;
|
|
76
|
+
}
|
|
77
|
+
export type SquadsAddressProblem =
|
|
78
|
+
/** One of the two addresses without the other. */
|
|
79
|
+
'incomplete'
|
|
80
|
+
/** FORGE_SQUADS_VAULT_INDEX other than the default 0 without the addresses. */
|
|
81
|
+
| 'index_without_multisig'
|
|
82
|
+
/** FORGE_SQUADS_VAULT_INDEX is not an integer between 0 and 255. */
|
|
83
|
+
| 'invalid_index'
|
|
84
|
+
/** The spending limit account is the multisig account. */
|
|
85
|
+
| 'not_distinct';
|
|
86
|
+
/** Thrown by `readSquadsAddresses` on a rule the names alone cannot express. Names only. */
|
|
87
|
+
export declare class ForgeSquadsConfigError extends Error {
|
|
88
|
+
readonly problem: SquadsAddressProblem;
|
|
89
|
+
constructor(problem: SquadsAddressProblem, message: string);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* FORGE_SQUADS_MULTISIG, FORGE_SQUADS_SPENDING_LIMIT and FORGE_SQUADS_VAULT_INDEX. Returns `null`
|
|
93
|
+
* when both addresses are absent (no withdrawer: the buyback stays in simulation). Rules: the
|
|
94
|
+
* two addresses are set together or not at all; an index other than the default `0` needs them
|
|
95
|
+
* (a lone `0` is the default spelled out, not a configuration); the index is an integer between
|
|
96
|
+
* 0 and 255 (default 0); the spending limit is a distinct account. Deriving the vault from the
|
|
97
|
+
* multisig and the index, and comparing it with FORGE_MULTISIG_VAULT, needs the Squads program
|
|
98
|
+
* id (pinned SDK): the caller (the signer) does it.
|
|
99
|
+
*
|
|
100
|
+
* @throws ForgeAddressError (invalid address, names only), ForgeSquadsConfigError (other rules).
|
|
101
|
+
*/
|
|
102
|
+
export declare function readSquadsAddresses(env?: AddressEnv): SquadsAddresses | null;
|
package/dist/addresses.js
CHANGED
|
@@ -23,10 +23,17 @@ export const FORGE_ADDRESS_NAMES = [
|
|
|
23
23
|
'FORGE_JUPITER_REFERRAL_ACCOUNT',
|
|
24
24
|
'FORGE_CASHBOX_WALLET',
|
|
25
25
|
'FORGE_MINT',
|
|
26
|
+
'FORGE_SQUADS_MULTISIG',
|
|
27
|
+
'FORGE_SQUADS_SPENDING_LIMIT',
|
|
26
28
|
];
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Addresses that may be absent. `FORGE_MINT` is absent before the $FORGE launch; the Squads
|
|
31
|
+
* accounts are absent while the buyback stays in simulation (see `readSquadsAddresses`).
|
|
32
|
+
*/
|
|
28
33
|
export const OPTIONAL_FORGE_ADDRESS_NAMES = [
|
|
29
34
|
'FORGE_MINT',
|
|
35
|
+
'FORGE_SQUADS_MULTISIG',
|
|
36
|
+
'FORGE_SQUADS_SPENDING_LIMIT',
|
|
30
37
|
];
|
|
31
38
|
/** Thrown when required addresses are missing or invalid. Holds names only, never values. */
|
|
32
39
|
export class ForgeAddressError extends Error {
|
|
@@ -101,6 +108,20 @@ export function requireAddresses(names, env = defaultEnv()) {
|
|
|
101
108
|
throw new ForgeAddressError(missing, invalid);
|
|
102
109
|
return result;
|
|
103
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* One FORGE address when it is set, `null` when it is absent or empty. A value that is set but is
|
|
113
|
+
* not a valid public key is refused, exactly as `requireAddresses` does. For checks that run
|
|
114
|
+
* 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).
|
|
116
|
+
*
|
|
117
|
+
* @throws ForgeAddressError (invalid value; names only).
|
|
118
|
+
*/
|
|
119
|
+
export function readOptionalAddress(name, env = defaultEnv()) {
|
|
120
|
+
const value = env[name];
|
|
121
|
+
if (value === undefined || value === '')
|
|
122
|
+
return null;
|
|
123
|
+
return requireAddresses([name], env)[name];
|
|
124
|
+
}
|
|
104
125
|
/** Squads multisig vault: receives the creator fees, the Meteora referral and the cashbox sweeps. */
|
|
105
126
|
export function getMultisigVault(env) {
|
|
106
127
|
return requireAddresses(['FORGE_MULTISIG_VAULT'], env).FORGE_MULTISIG_VAULT;
|
|
@@ -125,3 +146,51 @@ export function getCashboxWallet(env) {
|
|
|
125
146
|
export function getForgeMint(env) {
|
|
126
147
|
return requireAddresses(['FORGE_MINT'], env).FORGE_MINT;
|
|
127
148
|
}
|
|
149
|
+
/** Thrown by `readSquadsAddresses` on a rule the names alone cannot express. Names only. */
|
|
150
|
+
export class ForgeSquadsConfigError extends Error {
|
|
151
|
+
problem;
|
|
152
|
+
constructor(problem, message) {
|
|
153
|
+
super(message);
|
|
154
|
+
this.name = 'ForgeSquadsConfigError';
|
|
155
|
+
this.problem = problem;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const VAULT_INDEX_RE = /^(0|[1-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
|
|
159
|
+
/**
|
|
160
|
+
* FORGE_SQUADS_MULTISIG, FORGE_SQUADS_SPENDING_LIMIT and FORGE_SQUADS_VAULT_INDEX. Returns `null`
|
|
161
|
+
* when both addresses are absent (no withdrawer: the buyback stays in simulation). Rules: the
|
|
162
|
+
* two addresses are set together or not at all; an index other than the default `0` needs them
|
|
163
|
+
* (a lone `0` is the default spelled out, not a configuration); the index is an integer between
|
|
164
|
+
* 0 and 255 (default 0); the spending limit is a distinct account. Deriving the vault from the
|
|
165
|
+
* multisig and the index, and comparing it with FORGE_MULTISIG_VAULT, needs the Squads program
|
|
166
|
+
* id (pinned SDK): the caller (the signer) does it.
|
|
167
|
+
*
|
|
168
|
+
* @throws ForgeAddressError (invalid address, names only), ForgeSquadsConfigError (other rules).
|
|
169
|
+
*/
|
|
170
|
+
export function readSquadsAddresses(env = defaultEnv()) {
|
|
171
|
+
const multisigRaw = env.FORGE_SQUADS_MULTISIG ?? '';
|
|
172
|
+
const limitRaw = env.FORGE_SQUADS_SPENDING_LIMIT ?? '';
|
|
173
|
+
const indexRaw = env.FORGE_SQUADS_VAULT_INDEX ?? '';
|
|
174
|
+
if (multisigRaw === '' && limitRaw === '') {
|
|
175
|
+
if (indexRaw !== '' && indexRaw !== '0') {
|
|
176
|
+
throw new ForgeSquadsConfigError('index_without_multisig', 'FORGE_SQUADS_VAULT_INDEX needs FORGE_SQUADS_MULTISIG');
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
if (multisigRaw === '' || limitRaw === '') {
|
|
181
|
+
throw new ForgeSquadsConfigError('incomplete', 'FORGE_SQUADS_MULTISIG and FORGE_SQUADS_SPENDING_LIMIT must be set together');
|
|
182
|
+
}
|
|
183
|
+
const multisig = readOptionalAddress('FORGE_SQUADS_MULTISIG', env);
|
|
184
|
+
const spendingLimit = readOptionalAddress('FORGE_SQUADS_SPENDING_LIMIT', env);
|
|
185
|
+
let vaultIndex = 0;
|
|
186
|
+
if (indexRaw !== '') {
|
|
187
|
+
if (!VAULT_INDEX_RE.test(indexRaw)) {
|
|
188
|
+
throw new ForgeSquadsConfigError('invalid_index', 'FORGE_SQUADS_VAULT_INDEX must be an integer between 0 and 255');
|
|
189
|
+
}
|
|
190
|
+
vaultIndex = Number(indexRaw);
|
|
191
|
+
}
|
|
192
|
+
if (spendingLimit.equals(multisig)) {
|
|
193
|
+
throw new ForgeSquadsConfigError('not_distinct', 'FORGE_SQUADS_SPENDING_LIMIT must be a distinct account');
|
|
194
|
+
}
|
|
195
|
+
return { multisig, spendingLimit, vaultIndex };
|
|
196
|
+
}
|
package/dist/claims.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { PublicKey, Transaction, type Commitment, type Connection } from '@solana/web3.js';
|
|
2
2
|
import { type AddressEnv } from './addresses.js';
|
|
3
|
-
import { type RetryOptions, type TokenAmounts } from './reads.js';
|
|
3
|
+
import { type DbcPoolState, type RetryOptions, type TokenAmounts } from './reads.js';
|
|
4
4
|
/** Base fee per signature (lamports); claims set no priority fee. */
|
|
5
5
|
export declare const CLAIM_LAMPORTS_PER_SIGNATURE = 5000n;
|
|
6
6
|
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';
|
|
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' | 'not_migrated' | 'damm_pool_not_found' | 'damm_pool_mismatch' | 'position_not_found' | 'position_mismatch' | 'position_not_owned';
|
|
8
8
|
export declare class ClaimError extends Error {
|
|
9
9
|
readonly code: ClaimErrorCode;
|
|
10
10
|
constructor(code: ClaimErrorCode, message: string);
|
|
@@ -43,6 +43,12 @@ export interface ClaimSummary {
|
|
|
43
43
|
*/
|
|
44
44
|
receiverQuoteLamports: bigint;
|
|
45
45
|
quoteIsExact: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* WSOL the claimer's temporary account already held when the transaction was built and that
|
|
48
|
+
* the close hands to the receiver (counted in `receiverQuoteLamports`). Zero when the account
|
|
49
|
+
* is empty or new, and when the close returns it to a claimer paying its own claim.
|
|
50
|
+
*/
|
|
51
|
+
existingWsolLamports: bigint;
|
|
46
52
|
/** Zero costs when there is no transaction. */
|
|
47
53
|
costs: ClaimCosts;
|
|
48
54
|
/**
|
|
@@ -100,6 +106,10 @@ export declare function minimumWorthwhileClaimLamports(costs: ClaimCosts, minNet
|
|
|
100
106
|
* callers skip claims below it. Base tokens are not valued (FORGE configs collect SOL fees only).
|
|
101
107
|
*/
|
|
102
108
|
export declare function isClaimWorthwhile(summary: ClaimSummary, minNetLamports?: bigint): boolean;
|
|
109
|
+
/** @throws ClaimError `unsupported_quote_mint` unless the pool is quoted in SOL. */
|
|
110
|
+
export declare function assertSolQuotedPool(state: DbcPoolState): void;
|
|
111
|
+
/** @throws ClaimError `invalid_receiver` for anything but a usable wallet address. */
|
|
112
|
+
export declare function assertClaimReceiver(receiver: PublicKey): void;
|
|
103
113
|
/**
|
|
104
114
|
* Builds the creator claim of `pool` to `receiver` (see the module comment for the transaction).
|
|
105
115
|
* @throws ClaimError when `creator` is not the pool creator, the pool is not SOL-quoted, the
|
package/dist/claims.js
CHANGED
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
* exactly that amount of SOL to the receiver.
|
|
26
26
|
* So the payer's balance only decreases by the network fee and the one-time rent below, and the
|
|
27
27
|
* claimer's balance does not change at all when the claimer is not the payer (read-only signer).
|
|
28
|
-
* A claimer WSOL account that already holds WSOL
|
|
29
|
-
*
|
|
28
|
+
* A claimer WSOL account that already holds WSOL (anyone can send some to it) goes with the
|
|
29
|
+
* close: to the receiver when the receiver pays (accepted and counted in the summary as
|
|
30
|
+
* received, `existingWsolLamports`), back to the claimer when it pays its own claim (accepted).
|
|
31
|
+
* It is refused when the close would hand it to a payer that is neither (`claimer_wsol_not_empty`).
|
|
30
32
|
*
|
|
31
33
|
* Base side (the coin): the program needs a base token account even when there is nothing to
|
|
32
34
|
* claim (FORGE configs collect fees in SOL only). It is the receiver's associated account, created
|
|
@@ -66,12 +68,15 @@ export function minimumWorthwhileClaimLamports(costs, minNetLamports = 0n) {
|
|
|
66
68
|
export function isClaimWorthwhile(summary, minNetLamports = 0n) {
|
|
67
69
|
return summary.receiverQuoteLamports > 0n && summary.netLamports > minNetLamports;
|
|
68
70
|
}
|
|
69
|
-
|
|
71
|
+
/** @throws ClaimError `unsupported_quote_mint` unless the pool is quoted in SOL. */
|
|
72
|
+
export function assertSolQuotedPool(state) {
|
|
70
73
|
if (!state.config.quoteMint.equals(SOL_QUOTE_MINT)) {
|
|
71
74
|
throw new ClaimError('unsupported_quote_mint', `Pool ${state.address.toBase58()} is not quoted in SOL: FORGE pools are SOL-quoted only`);
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
|
-
|
|
77
|
+
const assertSolQuote = assertSolQuotedPool;
|
|
78
|
+
/** @throws ClaimError `invalid_receiver` for anything but a usable wallet address. */
|
|
79
|
+
export function assertClaimReceiver(receiver) {
|
|
75
80
|
if (!(receiver instanceof PublicKey)) {
|
|
76
81
|
throw new ClaimError('invalid_receiver', 'Claim receiver must be a PublicKey');
|
|
77
82
|
}
|
|
@@ -79,6 +84,7 @@ function assertReceiver(receiver) {
|
|
|
79
84
|
throw new ClaimError('invalid_receiver', 'Claim receiver is not a valid wallet');
|
|
80
85
|
}
|
|
81
86
|
}
|
|
87
|
+
const assertReceiver = assertClaimReceiver;
|
|
82
88
|
function isEmpty(amounts) {
|
|
83
89
|
return amounts.base === 0n && amounts.quote === 0n;
|
|
84
90
|
}
|
|
@@ -111,7 +117,13 @@ async function buildTradingClaim(connection, input, options) {
|
|
|
111
117
|
transaction: null,
|
|
112
118
|
lastValidBlockHeight: null,
|
|
113
119
|
skipReason: 'nothing_to_claim',
|
|
114
|
-
summary: {
|
|
120
|
+
summary: {
|
|
121
|
+
...baseSummary,
|
|
122
|
+
receiverQuoteLamports: 0n,
|
|
123
|
+
existingWsolLamports: 0n,
|
|
124
|
+
costs: ZERO_COSTS,
|
|
125
|
+
netLamports: 0n,
|
|
126
|
+
},
|
|
115
127
|
};
|
|
116
128
|
}
|
|
117
129
|
const baseMint = state.virtualPool.poolState.baseMint;
|
|
@@ -136,9 +148,11 @@ async function buildTradingClaim(connection, input, options) {
|
|
|
136
148
|
retryRpc(() => connection.getMinimumBalanceForRentExemption(ACCOUNT_SIZE, commitment), options.retry),
|
|
137
149
|
]);
|
|
138
150
|
const [receiverBaseInfo, claimerWsolInfo, receiverInfo] = infos;
|
|
139
|
-
// The temporary WSOL account is closed to the payer
|
|
140
|
-
//
|
|
141
|
-
|
|
151
|
+
// The temporary WSOL account is closed to the payer. WSOL it already holds (anyone can send
|
|
152
|
+
// some, 1 lamport is enough) goes with the close: fine when the payer is the receiver (it is
|
|
153
|
+
// received) or the claimer itself (its own); refused when it would reach a third-party payer.
|
|
154
|
+
let existingWsolLamports = 0n;
|
|
155
|
+
if (claimerWsolInfo && claimerWsolInfo.owner.equals(TOKEN_PROGRAM_ID)) {
|
|
142
156
|
let amount;
|
|
143
157
|
try {
|
|
144
158
|
amount = unpackAccount(claimerWsol, claimerWsolInfo, TOKEN_PROGRAM_ID).amount;
|
|
@@ -147,9 +161,11 @@ async function buildTradingClaim(connection, input, options) {
|
|
|
147
161
|
// Not a token account (e.g. lamports sent to the address): the idempotent creation takes it.
|
|
148
162
|
amount = 0n;
|
|
149
163
|
}
|
|
150
|
-
if (amount > 0n) {
|
|
164
|
+
if (amount > 0n && !claimer.equals(payer) && !receiver.equals(payer)) {
|
|
151
165
|
throw new ClaimError('claimer_wsol_not_empty', `The claimer's WSOL account ${claimerWsol.toBase58()} holds ${amount} lamports of WSOL: unwrap it before claiming with another payer`);
|
|
152
166
|
}
|
|
167
|
+
if (receiver.equals(payer))
|
|
168
|
+
existingWsolLamports = amount;
|
|
153
169
|
}
|
|
154
170
|
if (exact && quote > 0n) {
|
|
155
171
|
const size = receiverInfo?.data.length ?? 0;
|
|
@@ -189,11 +205,13 @@ async function buildTradingClaim(connection, input, options) {
|
|
|
189
205
|
payerTotalLamports,
|
|
190
206
|
payerRequiredBalanceLamports: payerTotalLamports + temporaryLamports,
|
|
191
207
|
};
|
|
208
|
+
const receiverQuoteLamports = quote + existingWsolLamports;
|
|
192
209
|
const summary = {
|
|
193
210
|
...baseSummary,
|
|
194
|
-
receiverQuoteLamports
|
|
211
|
+
receiverQuoteLamports,
|
|
212
|
+
existingWsolLamports,
|
|
195
213
|
costs,
|
|
196
|
-
netLamports:
|
|
214
|
+
netLamports: receiverQuoteLamports - payerTotalLamports,
|
|
197
215
|
};
|
|
198
216
|
if (options.minNetLamports !== undefined && !isClaimWorthwhile(summary, options.minNetLamports)) {
|
|
199
217
|
return { transaction: null, lastValidBlockHeight: null, skipReason: 'not_worthwhile', summary };
|
package/dist/config.d.ts
CHANGED
|
@@ -25,41 +25,10 @@
|
|
|
25
25
|
* `buildLaunchpadConfig` / `buildLaunchpadCoinConfig` are pure and deterministic. Every result
|
|
26
26
|
* passes `validate.ts` before it is returned; otherwise a `ValidationError` is thrown.
|
|
27
27
|
*/
|
|
28
|
-
import {
|
|
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
|
-
|
|
32
|
-
export declare const FORGE_TOKEN_SETTINGS: {
|
|
33
|
-
readonly tokenType: TokenType.SPLToken;
|
|
34
|
-
readonly tokenBaseDecimal: TokenDecimal.SIX;
|
|
35
|
-
readonly tokenQuoteDecimal: 9;
|
|
36
|
-
readonly tokenAuthorityOption: TokenAuthorityOption.Immutable;
|
|
37
|
-
readonly totalTokenSupply: 1000000000;
|
|
38
|
-
readonly leftover: 0;
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Anti-sniper fee schedule (`spec.antiSniper`): the fee starts at `startingFeeBps` and decreases
|
|
42
|
-
* linearly, in `numberOfPeriod` equal steps over `totalDurationSeconds`, to `spec.tradingFeeBps`.
|
|
43
|
-
* Within the validate.ts bounds (starting fee <= 9900 bps, duration <= 300 s).
|
|
44
|
-
*/
|
|
45
|
-
export declare const ANTI_SNIPER_FEE_SCHEDULE: {
|
|
46
|
-
readonly baseFeeMode: BaseFeeMode.FeeSchedulerLinear;
|
|
47
|
-
readonly startingFeeBps: 9900;
|
|
48
|
-
readonly numberOfPeriod: 10;
|
|
49
|
-
readonly totalDurationSeconds: 60;
|
|
50
|
-
};
|
|
51
|
-
/** Migration: DAMM v2, migrated pool fee 0.25%, no migration fee, 20% of the supply migrated. */
|
|
52
|
-
export declare const FORGE_MIGRATION_SETTINGS: {
|
|
53
|
-
readonly migrationOption: MigrationOption.MET_DAMM_V2;
|
|
54
|
-
readonly migrationFeeOption: MigrationFeeOption.FixedBps25;
|
|
55
|
-
readonly migrationFee: {
|
|
56
|
-
readonly feePercentage: 0;
|
|
57
|
-
readonly creatorFeePercentage: 0;
|
|
58
|
-
};
|
|
59
|
-
readonly percentageSupplyOnMigration: 20;
|
|
60
|
-
};
|
|
61
|
-
/** Fee scheduler periods are counted in seconds. */
|
|
62
|
-
export declare const FORGE_ACTIVATION_TYPE = ActivationType.Timestamp;
|
|
31
|
+
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';
|
|
63
32
|
/**
|
|
64
33
|
* Pool creation fee of the launchpad coin config: none. The only pool ever created on it is the
|
|
65
34
|
* client's own coin, paid by the client: a fee would only move the client's SOL to its own fee
|
|
@@ -69,9 +38,14 @@ export declare const FORGE_ACTIVATION_TYPE = ActivationType.Timestamp;
|
|
|
69
38
|
*/
|
|
70
39
|
export declare const LAUNCHPAD_COIN_POOL_CREATION_FEE_SOL = 0;
|
|
71
40
|
/** Pool creation fee of a config kind, in SOL. */
|
|
72
|
-
export declare function poolCreationFeeSolFor(spec: LaunchpadSpec, kind: ConfigKind): number;
|
|
41
|
+
export declare function poolCreationFeeSolFor(spec: Pick<LaunchpadSpec, 'poolCreationFeeSol'>, kind: ConfigKind): number;
|
|
73
42
|
/** Which of the two configs. */
|
|
74
43
|
export type ConfigKind = 'launchpad' | 'launchpadCoin';
|
|
44
|
+
/**
|
|
45
|
+
* The spec fields a config depends on, besides the owner wallet (a full `LaunchpadSpec` fits).
|
|
46
|
+
* Everything else in a FORGE config is a fixed setting (settings.ts).
|
|
47
|
+
*/
|
|
48
|
+
export type ConfigSpecFields = Pick<LaunchpadSpec, 'tradingFeeBps' | 'antiSniper' | 'coinCreatorSharePct' | 'poolCreationFeeSol'>;
|
|
75
49
|
/**
|
|
76
50
|
* What `createConfig` needs besides the config keypair and the payer: the SDK
|
|
77
51
|
* `ConfigParameters` plus the accounts that decide where the money goes.
|
|
@@ -92,9 +66,9 @@ export type LaunchpadConfigParams = ConfigParameters & {
|
|
|
92
66
|
*/
|
|
93
67
|
export declare function parseLaunchpadSpec(spec: unknown): LaunchpadSpec;
|
|
94
68
|
/** Creator share of the trading fees for each config kind. */
|
|
95
|
-
export declare function creatorTradingFeePercentageFor(spec: LaunchpadSpec, kind: ConfigKind): number;
|
|
96
|
-
/** The `buildCurve` input for a spec and a config kind (pure). */
|
|
97
|
-
export declare function curveParamsFor(spec:
|
|
69
|
+
export declare function creatorTradingFeePercentageFor(spec: Pick<LaunchpadSpec, 'coinCreatorSharePct'>, kind: ConfigKind): number;
|
|
70
|
+
/** The `buildCurve` input for a spec (or just its config fields) and a config kind (pure). */
|
|
71
|
+
export declare function curveParamsFor(spec: ConfigSpecFields, kind: ConfigKind): BuildCurveParams;
|
|
98
72
|
export interface ConfigParamsOptions {
|
|
99
73
|
/**
|
|
100
74
|
* Metadata URI the launchpad coin will be launched with, when known (the signer knows it from
|
|
@@ -115,6 +89,35 @@ export declare function buildConfigParams(spec: LaunchpadSpec, kind: ConfigKind,
|
|
|
115
89
|
export declare function buildLaunchpadConfig(spec: LaunchpadSpec): LaunchpadConfigParams;
|
|
116
90
|
/** Launchpad coin config: the launchpad's own coin, FORGE is the creator (25%). */
|
|
117
91
|
export declare function buildLaunchpadCoinConfig(spec: LaunchpadSpec): LaunchpadConfigParams;
|
|
92
|
+
/** What an on-chain config is expected to be, besides its kind. */
|
|
93
|
+
export interface OnchainConfigExpectation {
|
|
94
|
+
/**
|
|
95
|
+
* The spec the config was built from. Every field is then compared with
|
|
96
|
+
* `buildConfigParams(spec, kind)`, exactly what `buildCreateConfigTx` sent.
|
|
97
|
+
*/
|
|
98
|
+
spec?: LaunchpadSpec;
|
|
99
|
+
/** Fee claimer and leftover receiver (`spec.ownerWallet` when a spec is given). */
|
|
100
|
+
owner?: PublicKey;
|
|
101
|
+
/** Pool creation fee in lamports (launchpad config; the coin config never has one). */
|
|
102
|
+
poolCreationFeeLamports?: bigint;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Compares a config read from the chain with the parameters core builds for it, field by field:
|
|
106
|
+
* accounts, token, base fee (cliff, periods, period, reduction, mode), dynamic fee, creator share,
|
|
107
|
+
* pool creation fee, first swap at the minimum fee, activation, liquidity split and vesting,
|
|
108
|
+
* migration (option, fee option, threshold, migration fee percentages, migrated pool fee), locked
|
|
109
|
+
* vesting, token supply, start price and curve. The values the program derives from these
|
|
110
|
+
* (`swapBaseAmount`, `migrationBaseThreshold`, `migrationSqrtPrice`) are not compared.
|
|
111
|
+
*
|
|
112
|
+
* With `expected.spec`, the reference is `buildConfigParams(spec, kind)`. Without it, the spec
|
|
113
|
+
* fields are read from the config itself (`inferSpecFields`: they must be values a FORGE spec can
|
|
114
|
+
* hold) and every other field must be FORGE's fixed setting; the fee claimer and leftover receiver
|
|
115
|
+
* must be `expected.owner` (or, without it, the same key).
|
|
116
|
+
*
|
|
117
|
+
* @returns every mismatch, as readable messages (on-chain values are public); empty when the config
|
|
118
|
+
* is exactly a FORGE config of this kind.
|
|
119
|
+
*/
|
|
120
|
+
export declare function onchainConfigMismatches(config: PoolConfig, kind: ConfigKind, expected?: OnchainConfigExpectation): string[];
|
|
118
121
|
export interface FirstBuyQuote {
|
|
119
122
|
/** Raw base units (6 decimals) delivered by the first buy. */
|
|
120
123
|
tokensOut: bigint;
|