The service accepts and returns JSON. The OpenAPI 3.1 contract is available at docs/openapi.yaml.
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/launches |
Shared EVM launch route for static, multicurve, and dynamic auctions. It also accepts the shared-route Solana request shape. |
POST |
/v1/launches/static |
Family-specific route for static EVM auctions. |
POST |
/v1/launches/multicurve |
Family-specific route for multicurve EVM auctions. |
POST |
/v1/launches/dynamic |
Family-specific route for dynamic EVM auctions. |
POST |
/v1/solana/launches |
Dedicated Solana create route. |
GET |
/v1/launches/:launchId |
EVM launch transaction status. |
GET |
/v1/solana/launches/:launchAddress |
Solana launch account state. |
GET |
/v1/capabilities |
Enabled chains, modes, pricing, and Solana availability. |
GET |
/health |
Process liveness. |
GET |
/ready |
EVM and Solana dependency readiness. |
GET |
/metrics |
In-process HTTP metrics. |
Send x-api-key on every endpoint except GET /health. A missing or invalid key returns 401 UNAUTHORIZED. Rate limiting applies to every endpoint; an exceeded limit returns 429.
Every create route accepts Idempotency-Key. It is optional in standalone deployments and required in shared deployments. Reuse a key only with the exact same request body:
- A completed request is returned again with
X-Idempotency-Replayed: true. Replay happens before current schema validation, so an exact historical body remains replayable after a contract change. - Reusing a key with a different body returns
409 IDEMPOTENCY_KEY_REUSE_MISMATCH. 409 IDEMPOTENCY_KEY_IN_DOUBTmeans submission may have succeeded. Reconcile the recorded EVM signer and nonce, Solana signature, or returned launch and transaction identifiers before using a new key. Ambiguous submissions are not automatically resubmitted.
A confirmed pre-broadcast EVM token-address collision returns 409 TOKEN_ADDRESS_COLLISION. Pending chain state participates in collision and simulation checks.
Errors use one envelope:
{
"error": {
"code": "INVALID_REQUEST",
"message": "Request failed validation",
"details": {}
}
}details is optional. Validation failures and unknown keys in strict request objects return 422; Zod validation failures use INVALID_REQUEST. Server errors return the public message Internal server error and omit internal details.
| HTTP status | Meaning |
|---|---|
401 |
Missing or invalid API key. |
404 |
A requested Solana launch account does not exist. |
409 |
Idempotency conflict, uncertain submission, or token-address collision. |
422 |
Invalid fields, invalid field combinations, unavailable request default, or a mode not enabled for the selected chain. |
429 |
Rate limit exceeded. |
501 |
Solana is disabled or the recognized Mainnet Beta network is not executable. |
502 |
Chain lookup, simulation, submission, or response decoding failed. |
503 |
A required dependency is unavailable or an EVM nonce lock was lost before broadcast. |
POST /v1/launches accepts every EVM auction family. The three family-specific routes accept the same family schema but require their matching auction.type:
/v1/launches/staticrequires"static";/v1/launches/multicurverequires"multicurve";/v1/launches/dynamicrequires"dynamic".
All four routes return the same EVM create response. EVM request objects are strict: Solana-only feeBeneficiaries, unknown top-level keys, and unknown nested keys return 422 INVALID_REQUEST.
| Field | Required | Type and behavior |
|---|---|---|
chainId |
Conditional | Integer chain ID. Supported IDs are 1, 143, 4663, 8453, and 84532. Omission uses DEFAULT_CHAIN_ID; without a configured default it returns 422 CHAIN_ID_REQUIRED. An explicitly selected supported chain without its named RPC returns 422 CHAIN_NOT_CONFIGURED; there is no fallback to another chain. |
userAddress |
Yes | Non-zero EVM address that owns the launch. |
integrationAddress |
No | Non-zero EVM integrator address. |
tokenMetadata |
Yes | Token identity and optional balance-limit controls described below. |
economics |
Yes | Supply and optional vesting allocations described below. |
pairing.numeraireAddress |
No | EVM address. Defaults to the chain's configured numeraire and then its WETH address. If neither exists, the request returns 422 NUMERAIRE_REQUIRED. |
pricing.numerairePriceUsd |
No | Positive number overriding numeraire USD pricing. Otherwise the configured pricing source resolves the price. |
poolFeeBeneficiaries |
No | Up to 10 pool-fee recipients. It is not accepted with dynamic uniswapV2. |
governance |
No | false, true, or a non-zero EVM multisig address. See governance below. |
auction |
Yes | Static, multicurve, or dynamic family object. |
migration |
Dynamic only | Exactly one uniswapV2 or uniswapV4 migration. Static and multicurve requests reject it. |
| Field | Required | Validation and default |
|---|---|---|
name |
Yes | Non-empty string. |
symbol |
Yes | Non-empty string. |
tokenURI |
Yes | Non-empty string. |
maxBalanceLimit |
No | Positive decimal integer string. It must be lower than economics.totalSupply and must appear with balanceLimitEnd. |
balanceLimitEnd |
No | Future Unix timestamp from 0 through 281474976710655; it must appear with maxBalanceLimit. |
balanceController |
No | Non-zero EVM address. |
excludedFromBalanceLimit |
No | Case-insensitively unique EVM addresses. A non-empty list requires maxBalanceLimit and balanceLimitEnd. |
EVM launches use DopplerERC20V1. Supplying only one of the two balance-limit fields, a zero limit, an expired end time, or exclusions without an active limit returns 422. maxBalanceLimit, totalSupply, tokensForSale, each allocation amount, and each pool-beneficiary sharesWad are canonical positive values encoded as decimal strings without leading zeros. Contract-sized values cannot exceed 115792089237316195423570985008687907853269984665640564039457584007913129639935 (
| Field | Required | Validation and default |
|---|---|---|
totalSupply |
Yes | Positive decimal integer string. |
tokensForSale |
No | Positive decimal integer string no greater than totalSupply. With no allocations it defaults to totalSupply; with explicit allocations it defaults to totalSupply minus their sum. If lower than total supply, it must be at least 20% of total supply. |
allocations |
No | From 1 through 10 independent vesting schedules. Their amounts must equal totalSupply - tokensForSale. The same recipient may appear more than once. |
Each allocation contains:
| Field | Required | Validation and default |
|---|---|---|
recipientAddress |
Yes | Non-zero EVM address. |
amount |
Yes | Positive decimal integer string. |
durationSeconds |
Yes | Integer from 86400 through 4294967295. |
cliffDurationSeconds |
No | Integer from 0 through durationSeconds; defaults to 0. |
When tokensForSale leaves a remainder and allocations is omitted, the API creates one allocation to userAddress with a 90-day duration and no cliff. Allocations are invalid when the entire supply is for sale.
Each poolFeeBeneficiaries entry is { "address": <non-zero EVM address>, "sharesWad": <positive integer string> }. Addresses are unique case-insensitively.
- Omitted or empty uses 95% for
userAddressand 5% for the Airlock owner. If those addresses are the same, that address receives 100%. - If the request omits the Airlock owner, request shares must total
950000000000000000; the API appends the owner's 5% entry. - If the request includes the Airlock owner, all shares must total
1e18and that owner must receive at least 5%. - The 10-entry maximum includes an owner entry appended by the API.
Successful responses report effectiveConfig.poolFeeBeneficiariesSource as default, request, or none. Dynamic uniswapV2 uses none.
| Request value | External mode |
|---|---|
omitted or false |
noOp |
true |
default |
| non-zero EVM address | custom governance with that multisig |
The selected mode must appear in the chain's governanceModes, and non-noOp governance also requires governance to be enabled for that chain.
Static requests use:
{
"auction": {
"type": "static",
"curveConfig": {
"type": "preset",
"preset": "medium"
}
}
}curveConfig has one of two strict shapes.
Preset
| Field | Required | Validation and default |
|---|---|---|
type |
Yes |
"preset". |
preset |
Yes |
"low", "medium", or "high". The ranges are respectively $7,500–30,000, $50,000–150,000, and $250,000–750,000. |
fee |
No |
100, 500, 3000, or 10000; defaults to 10000. |
numPositions |
No | Integer from 1 through 65535; defaults to 15. |
maxShareToBeSoldWad |
No | Positive integer string no greater than 1e18; defaults to 350000000000000000. |
Range
| Field | Required | Validation and default |
|---|---|---|
type |
Yes | "range". |
marketCapStartUsd |
Yes | Positive number. |
marketCapEndUsd |
Yes | Positive number greater than marketCapStartUsd. |
fee |
No | 100, 500, 3000, or 10000; defaults to 10000. |
numPositions |
No | Integer from 1 through 65535; defaults to 15. |
maxShareToBeSoldWad |
No | Positive integer string no greater than 1e18; defaults to 350000000000000000. |
Static requests do not accept migration or a multicurve initializer.
Multicurve requests use auction.type: "multicurve", a preset or ranges curve, and a mandatory flattened Rehype initializer. They do not accept migration.
| Field | Required | Validation and default |
|---|---|---|
type |
Yes | "preset". |
presets |
No | Array containing "low", "medium", and/or "high". Omitted or empty selects all three. |
fee |
No | Integer from 0 through 100000; defaults to 500. |
tickSpacing |
No | Integer from 1 through 32767. When omitted, the API selects the largest spacing no greater than the fee-tier default that divides every boundary in the selected presets. |
| Field | Required | Validation and default |
|---|---|---|
type |
Yes | "ranges". |
fee |
No | Integer from 0 through 100000; defaults to 3000. |
tickSpacing |
No | Integer from 1 through 32767. Omitted built-in fee tiers use the SDK spacing; omitted custom fees derive a spacing from the fee. |
curves |
Yes | Non-empty array of contiguous curve entries. |
Each curve entry requires positive marketCapStartUsd, sharesWad, and integer numPositions from 1 through 65535. marketCapEndUsd is either a positive number greater than the start or "max". Numeric neighboring ranges must be exactly contiguous, "max" is valid only on the final range, and all positive sharesWad values must total 1e18.
auction.initializer has this wire shape:
{
"startFee": 100,
"endFee": 10,
"durationSeconds": 86400,
"startingTime": 0,
"feeDistributionInfo": {
"assetFeesToAssetBuybackWad": "0",
"assetFeesToNumeraireBuybackWad": "0",
"assetFeesToBeneficiaryWad": "1000000000000000000",
"assetFeesToLpWad": "0",
"numeraireFeesToAssetBuybackWad": "0",
"numeraireFeesToNumeraireBuybackWad": "0",
"numeraireFeesToBeneficiaryWad": "1000000000000000000",
"numeraireFeesToLpWad": "0"
},
"buybackDestination": "0x1111111111111111111111111111111111111111"
}| Field | Required | Validation and default |
|---|---|---|
startFee |
Yes | Integer from 0 through 800000. |
endFee |
No | Integer from 0 through 800000; defaults to startFee and cannot exceed it. |
durationSeconds |
No | Unsigned 32-bit integer; defaults to 0. It must be greater than 0 when startFee > endFee. |
startingTime |
No | Unsigned 32-bit integer; defaults to 0. |
feeDistributionInfo |
Yes | Eight non-negative WAD integer strings. The four assetFeesTo* values must total 1e18, and the four numeraireFeesTo* values must independently total 1e18. |
buybackDestination |
Exactly one routing form | Non-zero EVM address. |
rehypeFeeBeneficiaries |
Exactly one routing form | From 1 through 10 case-insensitively unique non-zero addresses with positive sharesWad values totaling 1e18. |
buybackDestination and rehypeFeeBeneficiaries are mutually exclusive. Rehype beneficiary routing is independent of top-level poolFeeBeneficiaries. Do not add the Airlock owner to account for the protocol fee; Rehype reserves that fee before routing the remainder.
Dynamic requests use:
{
"auction": {
"type": "dynamic",
"curveConfig": {
"type": "range",
"marketCapStartUsd": 500000,
"marketCapMinUsd": 50000,
"minProceeds": "1",
"maxProceeds": "2"
}
},
"migration": {}
}| Curve field | Required | Validation and default |
|---|---|---|
type |
Yes | "range". |
marketCapStartUsd |
Yes | Positive number. |
marketCapMinUsd |
Yes | Positive number lower than marketCapStartUsd. |
minProceeds |
Yes | Canonical non-negative decimal string without leading zeros and with at most 18 decimal places. Its value in 18-decimal units cannot exceed uint256; the largest value is 115792089237316195423570985008687907853269984665640564039457.584007913129639935. |
maxProceeds |
Yes | Same encoding and maximum as minProceeds; must be positive and not lower than minProceeds. |
durationSeconds |
No | Positive integer; defaults to 604800. |
epochLengthSeconds |
No | Positive integer; defaults to 43200 and must divide the effective duration evenly. |
fee |
No | Integer from 0 through 100000; defaults to 10000. |
tickSpacing |
Conditional | Integer from 1 through 30; defaults to 30. It is required when fee is not 100, 500, 3000, or 10000. |
gamma |
No | Integer from 1 through 8388607; defaults to the SDK-computed value and must be divisible by effective tick spacing. |
numPdSlugs |
No | Integer from 1 through 15; defaults to 5. |
{
"type": "uniswapV2",
"feeBeneficiary": {
"address": "0x1111111111111111111111111111111111111111",
"percentage": 25
}
}feeBeneficiary is optional. Its address is non-zero and percentage is an integer from 1 through 50. Top-level poolFeeBeneficiaries is invalid with this migration. The migration sends 95% of LP tokens to the migration recipient and 5% to a one-year locker; locker exit fees belong to the Airlock owner.
| Field | Required | Validation and default |
|---|---|---|
type |
Yes | "uniswapV4". |
fee |
Yes | Integer from 0 through 150000; fixed Uniswap V4 LP fee. |
tickSpacing |
Yes | Integer from 1 through 32767. |
lockDurationSeconds |
Yes | Unsigned 32-bit integer. |
rehype |
No | Rehype migrator configuration below. |
A plain V4 migration uses DopplerHookMigrator. Adding migration.rehype uses RehypeDopplerHookMigrator and requires:
| Rehype migration field | Required | Validation and default |
|---|---|---|
buybackDestination |
Yes | Non-zero EVM address. |
customFee |
Yes | Integer from 0 through 1000000; this static hook fee is separate from the V4 LP fee. |
feeRoutingMode |
No | "directBuyback" or "routeToBeneficiaryFees"; defaults to "directBuyback". |
feeDistributionInfo |
Yes | The same eight-field matrix and two independent 1e18 sum rules used by the multicurve initializer. |
routeToBeneficiaryFees accrues hook fees for collection by buybackDestination; it does not route through top-level poolFeeBeneficiaries.
A successful submission returns 200:
| Field | Type and meaning |
|---|---|
launchId |
<chainId>:<txHash> identifier used by the EVM status route. |
chainId |
Effective EVM chain ID. |
txHash |
Submitted 32-byte transaction hash. |
statusUrl |
Relative EVM status URL. |
predicted.tokenAddress |
Predicted token address. |
predicted.poolId |
Predicted 32-byte pool ID. |
predicted.gasEstimate |
Optional decimal integer string. |
effectiveConfig.tokensForSale |
Effective sale amount. |
effectiveConfig.allocationAmount |
Total vested allocation amount. |
effectiveConfig.vestingAllocations |
Effective allocation schedules. |
effectiveConfig.numeraireAddress |
Effective numeraire. |
effectiveConfig.numerairePriceUsd |
Effective USD price. |
effectiveConfig.poolFeeBeneficiariesSource |
default, request, or none. |
effectiveConfig.initializer |
Present for multicurve responses; the same flattened Rehype shape accepted in the request. |
A 200 response means the transaction was submitted, not that it is confirmed.
Solana create requests use one of two route-specific network forms:
POST /v1/launchesrequiresnetwork: "solanaDevnet"or"solanaMainnetBeta".POST /v1/solana/launchesaccepts optionalnetwork: "devnet"or"mainnet-beta"; omission uses the deployment's configured Solana default.
The Mainnet Beta values are recognized but return 501 SOLANA_NETWORK_UNSUPPORTED. Executable creation uses Devnet.
Creation retries Solana RPC HTTP 429 responses with bounded exponential backoff. A configured SOLANA_DEVNET_ALT_ADDRESS is reused when the signed transaction fits; oversized launches fall back to a launch-specific lookup table and return 422 SOLANA_TRANSACTION_TOO_LARGE if the rebuilt transaction still exceeds the packet limit.
| Field | Required | Validation and default |
|---|---|---|
network |
Route-dependent | Required on the shared route and optional on the dedicated route, using the values above. |
tokenMetadata |
Yes | name is 1–32 characters, symbol is 1–10 characters, and tokenURI is 1–200 characters. |
economics.totalSupply |
Yes | Positive unsigned 64-bit decimal integer string. |
economics.baseForDistribution |
No | Non-negative unsigned 64-bit integer string; defaults to "0". |
economics.baseForLiquidity |
No | Non-negative unsigned 64-bit integer string; defaults to "0". Together with distribution it must remain below total supply. |
pairing.numeraireAddress |
No | Valid Solana address. Defaults to WSOL; WSOL is the only supported numeraire. |
pricing.numerairePriceUsd |
No | Positive number. Otherwise the configured fixed price or CoinGecko mode resolves WSOL/USD; without a source the request returns 422 SOLANA_NUMERAIRE_PRICE_REQUIRED. |
feeBeneficiaries |
No | Up to 8 unique Solana addresses. A non-empty list uses positive shareBps values totaling 10000 and cannot include the initializer protocol beneficiary. |
governance |
No | Only false is accepted; omission has the same effect. |
migration |
No | type is "none"; optional CPMM controls are described below. |
auction |
Yes | XYK auction object described below. |
When custom fee beneficiaries are omitted, the service uses its configured default payer allocation unless the onchain protocol fee consumes the full share. A deployment whose payer is also the protocol beneficiary requires an explicit valid list.
migration.type is "none". supportCpmm defaults to false. When it is true, minimumQuoteRaise is a required positive unsigned 64-bit integer string. Non-zero baseForDistribution or baseForLiquidity is valid only when CPMM migration is enabled. All API-created launches use Doppler launch hook v1; CPMM migration registration and hook features are independent.
| Field | Required | Validation and default |
|---|---|---|
type |
Yes | "xyk". |
curveConfig.type |
Yes | "range". |
curveConfig.marketCapStartUsd |
Yes | Positive finite number. |
curveConfig.marketCapEndUsd |
Yes | Positive finite number greater than the start. The derived virtual reserves must fit unsigned 64-bit values and reproduce the requested range within the service tolerance. |
curveFeeBps |
No | Integer from 0 through 10000; an alias of swapFeeBps. |
swapFeeBps |
No | Integer from 0 through 10000. If both fee fields are present they must match. If both are omitted, the onchain initializer minimum applies; the effective fee must remain within its onchain bounds. |
allowBuy |
No | Boolean; defaults to true. |
allowSell |
No | Boolean; defaults to true. |
cosignerGate |
No | Doppler-managed cosigner gate through Doppler launch hook v1. |
dynamicFee |
No | Dynamic fee schedule through Doppler launch hook v1. |
cosignerGate requires type: "cosigner" and does not accept a caller-provided signer. The API resolves the canonical managed cosigner from the hook's onchain configuration. Optional expiry.mode is "disabled" or "unixTimestamp"; omitted or disabled expiry is indefinite, while timestamp mode requires a non-negative unsigned 64-bit integer string in expiry.value. The gate works with or without CPMM migration.
dynamicFee requires startFeeBps, endFeeBps, and durationSeconds. Both fees are integers from 0 through 10000; the end cannot exceed the start. Duration is a non-negative unsigned 32-bit integer string and must be non-zero for a decay. Optional startingTime is a non-negative signed 64-bit integer string and defaults to "0". The effective swap fee is the greater of the schedule fee and swapFeeBps; dynamic fees may be combined with managed cosigning.
Deterministic request validation runs before dependency readiness checks. Readiness failures return 503 SOLANA_NOT_READY, simulation failures return 422 SOLANA_SIMULATION_FAILED, and submission failures return 502 SOLANA_SUBMISSION_FAILED.
A successful submission returns 200 with:
launchId, the base58 launch PDA;network,signature,explorerUrl, andstatusUrl;predicted.tokenAddress,launchAuthorityAddress,launchFeeStateAddress,baseVaultAddress, andquoteVaultAddress;effectiveConfig.tokensForSale,allocationAmount,baseForDistribution,baseForLiquidity,allocationLockMode: "none",numeraireAddress,numerairePriceUsd,curveVirtualBase,curveVirtualQuote,curveFeeBps,swapFeeBps,feeBeneficiariesSource,feeBeneficiaries,allowBuy,allowSell, andtokenDecimals.
launchId must match <chainId>:0x<64 hex characters>. Invalid syntax returns 422 INVALID_LAUNCH_ID. The 200 response contains launchId, chainId, txHash, status, and confirmations.
| Status | Additional fields |
|---|---|
pending |
No result; the transaction exists without a receipt. |
not_found |
No result; neither transaction nor receipt is currently available. |
reverted |
error: { code: "TX_REVERTED", message }. |
confirmed |
result.tokenAddress, poolOrHookAddress, poolId, and decimal-string blockNumber. |
Provider or confirmed-transaction decoding failures return 502.
The path value must be a valid Solana address. Invalid syntax returns 422 SOLANA_INVALID_ADDRESS, a missing account returns 404 SOLANA_LAUNCH_NOT_FOUND, and an RPC lookup failure returns 502 SOLANA_LOOKUP_FAILED.
The 200 response contains network, launchAddress, phase: { code, label }, authority, namespace, baseMint, quoteMint, baseVault, quoteVault, baseTotalSupply, baseForDistribution, baseForLiquidity, baseForCurve, curveVirtualBase, curveVirtualQuote, curveFeeBps, swapFeeBps, allowBuy, allowSell, hookProgram, hookFlags, migratorProgram, quoteDeposited, and tokenDecimals.
This authenticated endpoint returns only configured EVM chains and never returns RPC URLs:
defaultChainIdis the configured request default ornull;pricingcontainsenabledandprovider;- each
chainsentry containschainId, enabledauctionTypes,multicurveInitializers,migrationModes,governanceModes, andgovernanceEnabled; - a chain with multicurve support reports only
"rehype"inmulticurveInitializers; - governance modes are drawn from
"noOp","default", and"custom"; - migration modes are drawn from
"uniswapV2"and"uniswapV4"; solanacontainsenabled,supportedNetworks,unsupportedNetworks,dedicatedRouteInputAliases,creationOnly,numeraireAddress, andpriceResolutionModes(request,fixed, and/orcoingecko).
Call this endpoint before assembling a request instead of assuming that every configured chain enables every mode.
This is the only unauthenticated endpoint. A healthy process returns 200:
{ "status": "ok" }It does not check RPC or Solana dependencies.
This authenticated endpoint checks each configured EVM RPC and the enabled Solana dependencies. It returns 200 when all checks pass or 503 when any check fails.
The response contains:
status: "ready" | "degraded";checks[]with EVMchainId,ok, and either decimal-stringlatestBlockor anerror;solana.enabled,solana.ok, optionalsolana.network, andsolana.checks[]entries namedrpcReachable,latestBlockhash,initializerConfig, oraddressLookupTable.
This authenticated endpoint returns 200 with:
startedAt, an ISO date-time;- non-negative integer
uptimeSec; http.totalRequests;http.byStatusClass, a map of status-class counters; and- non-negative
http.avgDurationMs.