A curve rule is a platform’s answer to “which launches am I willing to host?”.
GlobalConfig sets the protocol floor — supply at least 10M, at least 20% of supply sold on the curve, and so on — and those floors are deliberately wide so that every kind of platform fits under them. A curve rule is where your platform narrows them to the shape your product actually supports.Rules live in their own PlatformCurveRule account, one per (platform, GlobalConfig) pair. They can only narrow what the config already allows; a rule can never widen a protocol limit.The mental model
Three levels, from the outside in:- Constraints inside one group are ANDed. All of them must hold.
- Groups inside one rule are ORed. A launch is allowed as soon as it satisfies any single group.
A rule takes effect only while
PlatformConfig.restrict_curve_param is 1. At 0 the program does not read rules at all, which is also the switch you use to roll a rule out and to roll it back.
Constraints
A constraint is a(field, op, value) triple. Nothing else — no expressions, no nesting.
Gte for the floor and an Lte for the ceiling. The same (field, op) pair may not appear twice in one group, which is what stops you from writing two contradictory minimums.
Fields
Field ids are permanent. New fields are only ever appended, so an id never changes meaning once a rule account holds it.
The derived fields are the ones that make rules portable. Pinning
Supply and TotalFundRaisingB to exact numbers fixes one launch shape; constraining FundRaisingRateB fixes the relationship between them and lets a creator pick any supply that keeps it.
The nine playbooks
Each playbook below is one rule. Constraints are written as(field, op, value).
1. One standard tier
The simplest rule, and the exact behaviour the retired curve-parameter whitelist offered: one shape, pinned.
Any launch that deviates in any of the three is rejected with
CurveParamNotMatchPlatformRule.
2. A band instead of a number
The reason bands exist: a creator picks a fund-raising target you are comfortable with, without you enumerating every value.
One group, four constraints, and the creator has a 50–200 SOL corridor. Under the old whitelist this needed one entry per permitted value, and the cap of ten entries made it impossible.
3. Tiers side by side
Groups are ORed, so each tier is a group.
Order matters for compute, not for semantics: evaluation stops at the first group that matches, so put your most-used tier first.
4. A graduation-valuation band
FundRaisingRateB is TotalFundRaisingB / Supply in millionths. Constraining it caps how richly a token can graduate regardless of the supply the creator chose.
With a 1e12 supply and a 9-decimal quote mint,
85e9 / 1e12 × 1e6 = 85_000 sits inside that band. A creator who doubles the supply must roughly double the target to stay in it — which is the point. Two constraints replace what would otherwise be a table of (supply, target) pairs.
5. A migration floor
MigrateRateA is the share of supply that actually lands in the CPMM pool at graduation: Supply − TotalSellA − TotalLockedAmount, over supply. It is the depth of the graduated pool, and it is the one protocol knob with no platform-side equivalent before rules existed.
At least 15% of supply reaches the pool. A creator cannot sell 95% on the curve and leave a shallow book behind.
If the parameters do not add up — a locked amount larger than what is left after the curve sale — the derived value cannot be computed and the constraint fails closed, so the launch is rejected rather than silently allowed.
6. Vesting you actually enforce
GlobalConfig.max_lock_rate caps vesting from above. A rule can put a floor under it, and require a real cliff.
Between 5% and 20% of supply vested, with at least a 30-day cliff. Useful for a platform whose pitch is “no instant-unlock launches”.
7. Token-type gating
BaseTokenProgram and TransferFeeEnabled are independent, which matters: a Token-2022 mint without TransferFeeConfig reports TransferFeeEnabled = 0 just like an SPL Token mint does.
8. A conditional transfer-fee cap
There is no “if” operator, and none is needed — two groups express the condition.
A zero-rate
TransferFeeConfig does not slip through group 0: the extension is present, so TransferFeeEnabled is 1 and only group 1 can accept it.
9. A time-boxed promo, scheduled up front
UnixTimestamp is the block time of the launch, so a group can carry its own validity window. You write both groups today and the switchover happens on its own.
No transaction is needed at the boundary. The cost is two group slots instead of one.
Curve-type restrictions
Four fields readTotalSellA: TotalSellA, SellRateA, MigrateAmountA, and MigrateRateA. On a constant-product config the creator supplies that number. On a fixed-price or linear-price config the curve derives it instead, and the value the program compares against is 0, which would reject every launch.
Rather than let you write a rule that silently blocks your own config, the program refuses those four fields at write time on a non-constant-product config, with CurveRuleFieldNotSupportedByCurve. Only constant-product configs exist today, so in practice you will not meet this error.
Check before you send
Both directions of the on-chain check are available off-chain, so neither a creator nor a platform has to learn a rule by watching transactions revert.Version banner.
- SDK:
@raydium-io/raydium-sdk-v2@0.2.42-alphais the version every other code demo on this site is pinned to. The two helpers below arrive with the SDK release that ships curve-rule support; until then, port them from the program’splatform_curve_rule.rsor call the program and read the error code. - Cluster: test on Solana
devnetfirst — see Test on devnet first. - Program ID: see
reference/program-addresses
Before a launch: will these parameters pass?
checkLaunchAgainstCurveRule mirrors the program’s launch-time check exactly, including its fail-closed behaviour. Run it in your launch form and you can disable the submit button with a reason instead of letting the creator pay for a reverted transaction.
- A missing rule account, and a rule with no group, both pass. So does a group with no constraints. Pass
rule: undefinedfor a non-existent account; do not treat it as a rejection. - Uncomputable values fail closed. A zero supply has no rates, and a locked amount larger than what the curve sale leaves has no migrate amount.
actualcomes backundefinedand the constraint counts as unsatisfied, exactly as on-chain. - All failing constraints are reported, not just the first. The program short-circuits because it only needs a verdict; the helper collects everything so your form can list every problem at once.
UnixTimestamp near a boundary, treat a pass as provisional.
Before writing a rule: is this group valid?
checkCurveRuleGroupWritable mirrors the write-time validation of UpdatePlatformCurveRule — constraint ids, the duplicate (field, op) rule, both count limits, and the curve-type restriction. Run it in your platform admin tool before signing.
Passing this check means the transaction will not be rejected for being malformed. It says nothing about whether the rule is what you meant — a group can be perfectly valid and still reject every launch your UI can produce. That is what the launch-side check above is for: after writing a group, run every shape your product offers through
checkLaunchAgainstCurveRule and confirm each one still finds a group.Test on devnet first
Enablingrestrict_curve_param on mainnet changes what your creators can do, immediately, for every launch. Rehearse the whole sequence on devnet before you touch mainnet:
- Create a platform config and a rule on devnet, and write the same groups you intend to ship.
- Run every launch shape your UI can produce through
checkLaunchAgainstCurveRule, and confirm the verdicts are the ones you expect — both the shapes that should pass and the shapes that should be rejected. - Enable
restrict_curve_param, then actually launch a token that should pass and one that should be rejected. The second should fail withCurveParamNotMatchPlatformRule(6025), not withNotEnoughRemainingAccounts(6018) — the latter means your builder is not appending the rule PDA and the check is not really being exercised. - Only then repeat on mainnet, in the same order.
cluster: "devnet" when you load it, and take the devnet program ID from reference/program-addresses.
Step 3 is the one worth insisting on. The off-chain helper and the on-chain program are two implementations of the same rules, and a devnet launch is what proves they agree for your rule — including that your launch builder passes the account at all.
Operating a rule
The delegated manager
Editing rules is routine work; a platform admin key is usually a multisig.PlatformConfig.curve_rule_manager exists for exactly that: set it once through UpdatePlatformConfig::CurveRuleManager, and that hot wallet can then create, update, remove, and close rule accounts on its own. The platform admin retains the same power in parallel, so a lost manager key is recoverable — rotate it with another admin call.
Scope of a compromised manager key: it can loosen or delete your parameter rules, and it can reclaim a rule account’s rent. It cannot touch fee wallets, vesting, the CPMM config, cannot flip restrict_curve_param, and cannot break a GlobalConfig limit. Treat it as a configuration key, not a treasury key.
Rent follows the content
A rule account is created holding no group and resized on every change, so you pay for the rules you actually wrote. Removing a group refunds the difference to the signer.Rollout order
- Rehearse the whole sequence on devnet — see Test on devnet first.
- Create the rule account and write its groups. Nothing changes yet — with
restrict_curve_paramstill0the program does not read them. - Check the rule off-chain with
checkLaunchAgainstCurveRule: for every launch shape your UI can produce, confirm some group accepts it. - Set
restrict_curve_paramto1. From that moment your creators’ launches are checked. - To roll back, set it to
0again. The rule account is left intact.
Cost at launch time
The check runs on every launch while it is enabled, so its compute cost is a per-launch tax. Measured end to end — PDA derivation, theremaining_accounts scan, deserialization, and evaluation:
“Cheap” is a direct field read such as
Supply; “derived” is a computed one such as MigrateRateA, which costs about 223 CU per constraint against about 48. Even a fully-loaded rule of derived constraints stays inside a third of the default 200 000 CU per-instruction budget, and a realistic three-group rule is under 6 000. Groups are evaluated until one matches, so ordering your common tier first is free savings.
Where to go next
products/launchlab/platform-config— thePlatformConfigfields that gate and delegate rules.products/launchlab/global-config— the protocol floors a rule narrows.products/launchlab/instructions— the four rule instructions and their accounts.products/launchlab/accounts—PlatformCurveRulein the account inventory.sdk-api/typescript-sdk— the SDK surface the two check helpers live in.reference/changelog/2026-08-31-launchlab-platform-curve-rules— what replaced the curve-parameter whitelist, and what decoders must change.
raydium-launch/programs/launchpad/src/states/platform_curve_rule.rs—PlatformCurveRule,CurveRuleGroup,ParamConstraint, the field and op id spaces, andCurveRuleContext::value_of.raydium-launch/programs/launchpad/src/utils/platform_curve_rule.rs— the launch-time check.raydium-launch/programs/launchpad/src/instructions/platform/—create,update,remove, andclose_platform_curve_rule.

