Launch Types
Launching a Bonding Curve via the Metaplex API
Last updated April 9, 2026
Use the Genesis SDK and the Metaplex API to create, sign, send, and register a bonding curve token launch on Solana.
What You'll Build
This guide covers:
- Launching a bonding curve token in a single call with
createAndRegisterLaunch - Adding creator fees — to a specific wallet or to an agent PDA automatically
- Configuring a fee-free first buy at launch
- Signing and registering the launch manually with
createLaunch+registerLaunch - Testing on devnet and using a custom API base URL or transaction sender
- Handling typed SDK errors
Summary
createAndRegisterLaunch (or its lower-level equivalents) calls POST /v1/launches/create, returns unsigned Solana transactions, signs and sends them, then registers the launch so the token appears on metaplex.com.
- One-liner path —
createAndRegisterLaunchhandles the full flow in a single awaited call - Manual path —
createLaunch+signAndSendLaunchTransactions+registerLaunchfor custom signing, bundles, or retry logic - Creator fees — optional per-swap fee earned on the bonding curve and in the post-graduation Raydium pool; configurable per-wallet or derived automatically for agent launches
- First buy — optional fee-free initial purchase reserved for the launching wallet or agent PDA at curve creation
Quick Start
Jump to: Installation · Setup · One-Liner Launch · Creator Fees · First Buy · Manual Signing · Token Metadata · Devnet · Advanced · Errors · API Reference
- Install the Genesis SDK and configure a Umi instance with your keypair identity
- Call
createAndRegisterLaunchwith yourtokenmetadata and alaunch: {}object - Read
result.mintAddressandresult.launch.linkfrom the response
For custom signing or retry logic, use Manual Signing Flow instead.
Prerequisites
- Node.js 18+ — required for native
BigIntsupport - A Solana wallet keypair funded with SOL for transaction fees and the optional first buy amount
- A Solana RPC endpoint (mainnet-beta or devnet)
- An image pre-uploaded to Irys — the token metadata
imagefield must be an Irys gateway URL
Installation
Install the three required packages.
npm install @metaplex-foundation/genesis \
@metaplex-foundation/umi \
@metaplex-foundation/umi-bundle-defaults
Umi Setup
Configure a Umi instance with your keypair identity before calling any Genesis API function.
1import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
2import { keypairIdentity } from '@metaplex-foundation/umi';
3
4const umi = createUmi('https://api.mainnet-beta.solana.com');
5
6// Load your keypair — use your preferred key management solution in production.
7const keypair = umi.eddsa.createKeypairFromSecretKey(mySecretKeyBytes);
8umi.use(keypairIdentity(keypair));
The Genesis API functions do not require the genesis() plugin — they talk to the hosted Metaplex API over HTTP rather than submitting instructions directly. The Umi instance is used only for its signer identity and transaction-sending capability.
Launching a Bonding Curve (One-Liner Flow)
createAndRegisterLaunch is the simplest path — it creates the launch, signs and sends all transactions, and registers the token on metaplex.com in one awaited call.
1import { createAndRegisterLaunch } from '@metaplex-foundation/genesis/api';
2import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
3import { keypairIdentity } from '@metaplex-foundation/umi';
4
5const umi = createUmi('https://api.mainnet-beta.solana.com');
6const keypair = umi.eddsa.createKeypairFromSecretKey(mySecretKeyBytes);
7umi.use(keypairIdentity(keypair));
8
9const result = await createAndRegisterLaunch(umi, {}, {
10 wallet: umi.identity.publicKey,
11 launchType: 'bondingCurve',
12 token: {
13 name: 'My Token',
14 symbol: 'MTK',
15 image: 'https://gateway.irys.xyz/your-image-id',
16 },
17 launch: {},
18});
19
20console.log('Token launched!');
21console.log('Mint address:', result.mintAddress);
22console.log('View at:', result.launch.link);
23
24// Token launched!
25// Mint address: <base58 mint address>
26// View at: https://www.metaplex.com/...
1mplx genesis launch create --launchType bonding-curve \
2 --name "My Token" \
3 --symbol "MTK" \
4 --image "https://gateway.irys.xyz/your-image-id"
All protocol parameters — supply splits, virtual reserves, fund flows, and lock schedules — are set to protocol defaults when launch: {} is empty. The sections below show how to add creator fees and a first buy.
Creator Fees
An optional per-swap fee accrues to a configured wallet on every buy and sell. Set creatorFeeWallet in the launch object to redirect fees to a specific address; the launching wallet is used by default.
launch: {
creatorFeeWallet: 'FeeRecipientWalletAddress...',
},
For full configuration options, how to check the accrued balance, and the claiming instructions (claimBondingCurveCreatorFeeV2 / claimRaydiumCreatorFeeV2), see Creator Fees.
First Buy
The first buy reserves the initial swap on the curve for the launching wallet at a specified SOL amount, with all fees waived.
Set firstBuyAmount to the SOL amount for the fee-free initial purchase.
1import { createAndRegisterLaunch } from '@metaplex-foundation/genesis/api';
2import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
3import { keypairIdentity } from '@metaplex-foundation/umi';
4
5const umi = createUmi('https://api.mainnet-beta.solana.com');
6const keypair = umi.eddsa.createKeypairFromSecretKey(mySecretKeyBytes);
7umi.use(keypairIdentity(keypair));
8
9const result = await createAndRegisterLaunch(umi, {}, {
10 wallet: umi.identity.publicKey,
11 launchType: 'bondingCurve',
12 token: {
13 name: 'My Token',
14 symbol: 'MTK',
15 image: 'https://gateway.irys.xyz/your-image-id',
16 },
17 launch: {
18 firstBuyAmount: 0.1, // 0.1 SOL, fee-free initial purchase
19 },
20});
1mplx genesis launch create --launchType bonding-curve \
2 --name "My Token" \
3 --symbol "MTK" \
4 --image "https://gateway.irys.xyz/your-image-id" \
5 --firstBuyAmount 0.1
The API executes the first buy as part of the launch transaction flow — the curve already has the initial purchase applied once the transactions confirm. The buyer defaults to the launching wallet, or to the agent PDA when agent is provided. Override with firstBuyWallet (a Signer) to designate a different buyer.
When firstBuyAmount is omitted or 0, no first buy restriction is applied and any wallet can make the first swap.
You can combine a first buy with a creator fee wallet:
launch: {
creatorFeeWallet: 'FeeRecipientWalletAddress...',
firstBuyAmount: 0.5,
},
Manual Signing Flow
Use createLaunch and registerLaunch separately when you need control over how transactions are signed and submitted — for example, when using Jito bundles, priority fees, or custom retry logic.
1import {
2 createLaunch,
3 registerLaunch,
4 signAndSendLaunchTransactions,
5} from '@metaplex-foundation/genesis/api';
6
7// Step 1: Call the API to get unsigned transactions.
8const createResult = await createLaunch(umi, {}, {
9 wallet: umi.identity.publicKey,
10 launchType: 'bondingCurve',
11 token: {
12 name: 'My Token',
13 symbol: 'MTK',
14 image: 'https://gateway.irys.xyz/your-image-id',
15 },
16 launch: {
17 creatorFeeWallet: 'FeeRecipientWalletAddress...',
18 },
19});
20
21console.log('Mint address:', createResult.mintAddress);
22console.log('Transactions to sign:', createResult.transactions.length);
23
24// Step 2: Sign and send the transactions.
25const signatures = await signAndSendLaunchTransactions(umi, createResult);
26
27// Step 3: Register the launch after all transactions are confirmed onchain.
28const registered = await registerLaunch(umi, {}, {
29 genesisAccount: createResult.genesisAccount,
30 createLaunchInput: {
31 wallet: umi.identity.publicKey,
32 launchType: 'bondingCurve',
33 token: {
34 name: 'My Token',
35 symbol: 'MTK',
36 image: 'https://gateway.irys.xyz/your-image-id',
37 },
38 launch: {
39 creatorFeeWallet: 'FeeRecipientWalletAddress...',
40 },
41 },
42});
43
44console.log('Launch live at:', registered.launch.link);
Call registerLaunch only after the create transactions are confirmed onchain. The API verifies that the genesis account exists before registering — calling it too early will return an API error.
Token Metadata
Every launch requires a token object with the following fields.
| Field | Required | Constraints |
|---|---|---|
name | Yes | 1–32 characters |
symbol | Yes | 1–10 characters |
image | Yes | Must be an Irys URL (https://gateway.irys.xyz/...) |
description | No | Max 250 characters |
externalLinks | No | Optional website, twitter, and telegram values |
token: {
name: 'My Token',
symbol: 'MTK',
image: 'https://gateway.irys.xyz/your-image-id',
description: 'A token launched on the bonding curve',
externalLinks: {
website: 'https://mytoken.com',
twitter: '@mytoken',
telegram: '@mytoken',
},
},
Devnet Testing
Pass network: 'solana-devnet' and point the Umi instance at the devnet RPC endpoint to route the launch through devnet infrastructure. For the CLI, the network is determined by your configured RPC endpoint.
1import { createAndRegisterLaunch } from '@metaplex-foundation/genesis/api';
2import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
3import { keypairIdentity } from '@metaplex-foundation/umi';
4
5const umi = createUmi('https://api.devnet.solana.com');
6const keypair = umi.eddsa.createKeypairFromSecretKey(mySecretKeyBytes);
7umi.use(keypairIdentity(keypair));
8
9const result = await createAndRegisterLaunch(umi, {}, {
10 wallet: umi.identity.publicKey,
11 launchType: 'bondingCurve',
12 network: 'solana-devnet',
13 token: {
14 name: 'Test Token',
15 symbol: 'TEST',
16 image: 'https://gateway.irys.xyz/test-image',
17 },
18 launch: {},
19});
1# Network is determined by your configured RPC endpoint.
2mplx genesis launch create --launchType bonding-curve \
3 --name "Test Token" \
4 --symbol "TEST" \
5 --image "https://gateway.irys.xyz/test-image"
Advanced
Custom API Base URL
The SDK defaults to https://api.metaplex.com. Pass baseUrl in the config object (the second argument) to target a different environment such as a staging API.
1const API_CONFIG = { baseUrl: 'https://your-api-base-url.example.com' };
2
3const result = await createAndRegisterLaunch(umi, API_CONFIG, {
4 wallet: umi.identity.publicKey,
5 launchType: 'bondingCurve',
6 token: {
7 name: 'My Token',
8 symbol: 'MTK',
9 image: 'https://gateway.irys.xyz/your-image-id',
10 },
11 launch: {},
12});
The same API_CONFIG object is accepted by createLaunch and registerLaunch in the manual signing flow.
Custom Transaction Sender
Pass a txSender callback in the options (fourth argument) to use your own signing and submission infrastructure.
1const result = await createAndRegisterLaunch(
2 umi,
3 {},
4 {
5 wallet: umi.identity.publicKey,
6 launchType: 'bondingCurve',
7 token: {
8 name: 'My Token',
9 symbol: 'MTK',
10 image: 'https://gateway.irys.xyz/your-image-id',
11 },
12 launch: {},
13 },
14 {
15 txSender: async (txs) => {
16 const signatures = [];
17 for (const tx of txs) {
18 const signed = await umi.identity.signTransaction(tx);
19 signatures.push(await myCustomSend(signed));
20 }
21 return signatures;
22 },
23 }
24);
Common Errors
| Error | Type check | Cause | Fix |
|---|---|---|---|
Validation error on "token.image" | isGenesisValidationError | Image URL is not an Irys gateway URL | Upload the image to Irys and use the https://gateway.irys.xyz/... URL |
Validation error on "token.name" | isGenesisValidationError | Name exceeds 32 characters or is empty | Shorten the token name to 1–32 characters |
Network error | isGenesisApiNetworkError | Cannot reach https://api.metaplex.com | Check connectivity or supply a baseUrl pointing to an accessible endpoint |
API error (4xx) | isGenesisApiError | Invalid input rejected by the API | Read err.responseBody for the field-level error detail |
API error (5xx) | isGenesisApiError | Metaplex API is unavailable | Retry with exponential back-off; do not re-send already-confirmed transactions |
registerLaunch API error | isGenesisApiError | Registered before create transactions confirmed | Wait for all signatures to confirm onchain before calling registerLaunch |
Use the typed error guards to distinguish these cases:
1import {
2 createLaunch,
3 isGenesisApiError,
4 isGenesisApiNetworkError,
5 isGenesisValidationError,
6} from '@metaplex-foundation/genesis/api';
7
8try {
9 const result = await createLaunch(umi, {}, input);
10} catch (err) {
11 if (isGenesisValidationError(err)) {
12 console.error(`Validation error on "${err.field}": ${err.message}`);
13 } else if (isGenesisApiNetworkError(err)) {
14 console.error('Network error:', err.message);
15 } else if (isGenesisApiError(err)) {
16 console.error(`API error (${err.statusCode}): ${err.message}`);
17 console.error('Details:', err.responseBody);
18 } else {
19 throw err;
20 }
21}
Notes
createAndRegisterLaunchis atomic from the caller's perspective but internally makes two API calls — a failure after the create transactions confirm but beforeregisterLaunchmeans the token exists onchain but is not yet visible on metaplex.com; callregisterLaunchmanually to complete registration- The Metaplex API endpoint (
https://api.metaplex.com) is hosted infrastructure — it constructs and returns unsigned transactions; the caller always holds and controls signing - Virtual reserves, supply splits, and lock schedules are set by protocol defaults when
launch: {}is empty; there is no API to override these per-launch - The
agent.setTokenflag is irreversible — once a token is set as an agent's primary token it cannot be changed or reassigned; see Create an Agent Token for the full agent launch flow - Once a curve is live, integrate swaps using the Bonding Curve Swap Integration guide
- First buy is configured at launch creation and cannot be added after the curve is live;
firstBuyAmount: 0or omitting the field disables it entirely - Creator fees are accrued in the bucket, not transferred per-swap; claim via the permissionless
claimBondingCurveCreatorFeeV2(bonding curve) andclaimRaydiumCreatorFeeV2(post-graduation Raydium) instructions
API Reference
createAndRegisterLaunch(umi, config, input, options?)
Convenience function that orchestrates the full launch flow: create, sign, send, and register.
| Parameter | Type | Description |
|---|---|---|
umi | Umi | Umi instance with identity and RPC configured |
config | GenesisApiConfig | null | Optional API config (baseUrl, custom fetch) |
input | CreateBondingCurveLaunchInput | Launch configuration |
options | SignAndSendOptions | Optional txSender override |
registerOptions | RegisterOptions | Optional fields forwarded to registerLaunch (e.g. creatorWallet, twitterVerificationToken) |
Returns Promise<CreateAndRegisterLaunchResult>:
| Field | Description |
|---|---|
signatures | Transaction signatures |
mintAddress | Created token mint address |
genesisAccount | Genesis account PDA |
launch.link | URL to view the token on metaplex.com |
createLaunch(umi, config, input)
Calls POST /v1/launches/create and returns deserialized transactions.
Returns Promise<CreateLaunchResponse>:
| Field | Description |
|---|---|
transactions | Array of Umi Transaction objects to sign and send |
blockhash | Blockhash for transaction validity |
mintAddress | Created token mint address |
genesisAccount | Genesis account PDA |
registerLaunch(umi, config, input)
Registers a confirmed genesis account on metaplex.com. Call after all create transactions are confirmed onchain.
Returns Promise<RegisterLaunchResponse>:
| Field | Description |
|---|---|
launch.id | Launch identifier |
launch.link | URL to view the token |
token.mintAddress | Confirmed mint address |
Types
interface CreateBondingCurveLaunchInput {
wallet: PublicKey | string;
launchType: 'bondingCurve';
token: TokenMetadata;
network?: 'solana-mainnet' | 'solana-devnet';
quoteMint?: 'SOL';
agent?: {
mint: PublicKey | string; // Core asset (NFT) address
setToken: boolean; // set launched token as the agent's primary token
};
launch: BondingCurveLaunchInput;
}
interface BondingCurveLaunchInput {
creatorFeeWallet?: PublicKey | string;
firstBuyAmount?: number; // SOL amount (e.g. 0.1 = 0.1 SOL)
firstBuyWallet?: Signer;
}
interface TokenMetadata {
name: string; // max 32 characters
symbol: string; // max 10 characters
image: string; // must be an Irys URL: https://gateway.irys.xyz/...
description?: string; // max 250 characters
externalLinks?: {
website?: string;
twitter?: string;
telegram?: string;
};
}
interface GenesisApiConfig {
baseUrl?: string;
fetch?: typeof fetch;
}
FAQ
What is the difference between createAndRegisterLaunch and calling createLaunch then registerLaunch separately?
createAndRegisterLaunch is a convenience wrapper that handles the full flow in a single call. Use the lower-level functions separately when you need custom signing logic (e.g. Jito bundles, priority fees) or when you want to inspect or modify the unsigned transactions before submission. See Manual Signing Flow.
Can I test a bonding curve launch on devnet before going to mainnet?
Yes. Pass network: 'solana-devnet' in the input and point your Umi instance at https://api.devnet.solana.com. The API routes the request to devnet infrastructure. Make sure the wallet is funded with devnet SOL before sending transactions. See Devnet Testing.
What happens if I set agent.setToken: true by mistake?
Setting setToken: true permanently associates the launched token with the agent as its primary token — this is irreversible and cannot be undone or reassigned. If you are unsure, omit the agent field or set setToken: false and handle token association separately.
Can I combine a creator fee wallet with a first buy?
Yes. Set both creatorFeeWallet and firstBuyAmount in the launch object. The first buy itself is fee-free — no protocol fee or creator fee is charged on that initial purchase. Creator fees apply normally to all subsequent swaps. See First Buy.
What image format and hosting does the token metadata require?
The image field must be an Irys URL — https://gateway.irys.xyz/<id>. Upload your image to Irys first and use the returned gateway URL. Other hosts will fail API validation. The SDK surfaces this as an isGenesisValidationError on the token.image field.
Why must registerLaunch be called after the transactions confirm onchain?
registerLaunch writes the launch record to Metaplex's database and verifies the genesis account exists onchain before registering. Calling it before the create transactions confirm will return an API error because the account is not yet visible. In createAndRegisterLaunch, this sequencing is handled automatically.
