Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
376 changes: 281 additions & 95 deletions src/commands/genesis/create.ts

Large diffs are not rendered by default.

150 changes: 123 additions & 27 deletions src/commands/genesis/launch/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ora from 'ora'
import { TransactionCommand } from '../../../TransactionCommand.js'
import { generateExplorerUrl } from '../../../explorers.js'
import { readJsonSync } from '../../../lib/file.js'
import { promptLaunchWizard } from '../../../lib/genesis/createGenesisWizardPrompt.js'
import { detectSvmNetwork, txSignatureToString } from '../../../lib/util.js'

export default class GenesisLaunchCreate extends TransactionCommand<typeof GenesisLaunchCreate> {
Expand All @@ -27,17 +28,25 @@ This is an all-in-one command that:
The Genesis API handles creating the genesis account, mint, launch pool bucket,
and optional locked allocations in a single flow.

Use --wizard for an interactive setup process.

Launch types:
- project: Total supply 1B, 48-hour deposit period, configurable allocations.
- memecoin: Total supply 1B, 1-hour deposit period, hardcoded fund flows. Only --depositStartTime is required.`

static override examples = [
'$ mplx genesis launch create --wizard',
'$ mplx genesis launch create --name "My Token" --symbol "MTK" --image "https://gateway.irys.xyz/abc123" --tokenAllocation 500000000 --depositStartTime 2025-03-01T00:00:00Z --raiseGoal 200 --raydiumLiquidityBps 5000 --fundsRecipient <ADDRESS>',
'$ mplx genesis launch create --launchType memecoin --name "My Meme" --symbol "MEME" --image "https://gateway.irys.xyz/abc123" --depositStartTime 2025-03-01T00:00:00Z',
'$ mplx genesis launch create --name "My Token" --symbol "MTK" --image "https://gateway.irys.xyz/abc123" --tokenAllocation 500000000 --depositStartTime 2025-03-01T00:00:00Z --raiseGoal 200 --raydiumLiquidityBps 5000 --fundsRecipient <ADDRESS> --lockedAllocations allocations.json',
]

static override flags = {
wizard: Flags.boolean({
description: 'Use interactive wizard to create a genesis launch',
required: false,
}),

// Launch type
launchType: Flags.option({
description: 'Launch type: project (default) or memecoin',
Expand All @@ -49,16 +58,16 @@ Launch types:
name: Flags.string({
char: 'n',
description: 'Name of the token (1-32 characters)',
required: true,
required: false,
}),
symbol: Flags.string({
char: 's',
description: 'Symbol of the token (1-10 characters)',
required: true,
required: false,
}),
image: Flags.string({
description: 'Token image URL (must start with https://gateway.irys.xyz/)',
required: true,
required: false,
}),
description: Flags.string({
description: 'Token description (max 250 characters)',
Expand All @@ -80,7 +89,7 @@ Launch types:
// Shared config
depositStartTime: Flags.string({
description: 'Deposit start time (ISO date string or unix timestamp). Project: 48h deposit. Memecoin: 1h deposit.',
required: true,
required: false,
}),

// Project-only launchpool config
Expand Down Expand Up @@ -127,6 +136,16 @@ Launch types:
public async run(): Promise<unknown> {
const { flags } = await this.parse(GenesisLaunchCreate)

if (flags.wizard) {
return this.runWizard(flags)
}

// Validate required flags for non-wizard mode
if (!flags.name) this.error('--name is required (or use --wizard for interactive mode)')
if (!flags.symbol) this.error('--symbol is required (or use --wizard for interactive mode)')
if (!flags.image) this.error('--image is required (or use --wizard for interactive mode)')
if (!flags.depositStartTime) this.error('--depositStartTime is required (or use --wizard for interactive mode)')

const isMemecoin = flags.launchType === 'memecoin'

// Reject project-only flags for memecoin launches
Expand Down Expand Up @@ -154,47 +173,124 @@ Launch types:
}
}

await this.executeLaunch({
launchType: flags.launchType,
name: flags.name,
symbol: flags.symbol,
image: flags.image,
description: flags.description,
website: flags.website,
twitter: flags.twitter,
telegram: flags.telegram,
depositStartTime: flags.depositStartTime,
quoteMint: flags.quoteMint,
tokenAllocation: flags.tokenAllocation,
raiseGoal: flags.raiseGoal,
raydiumLiquidityBps: flags.raydiumLiquidityBps,
fundsRecipient: flags.fundsRecipient,
lockedAllocationsFile: flags.lockedAllocations,
network: flags.network,
apiUrl: flags.apiUrl,
})
}

private async runWizard(flags: { network?: 'solana-mainnet' | 'solana-devnet'; apiUrl: string }): Promise<void> {
this.log(
`--------------------------------

Welcome to the Genesis Launch Wizard!

This wizard will guide you through creating a new token launch
via the Genesis API (all-in-one flow).

Type 'q' at any prompt to abort.

--------------------------------`
)

const result = await promptLaunchWizard()

await this.executeLaunch({
launchType: result.launchType,
name: result.name,
symbol: result.symbol,
image: result.image,
description: result.description,
website: result.website,
twitter: result.twitter,
telegram: result.telegram,
depositStartTime: result.depositStartTime,
quoteMint: result.quoteMint,
tokenAllocation: result.tokenAllocation,
raiseGoal: result.raiseGoal,
raydiumLiquidityBps: result.raydiumLiquidityBps,
fundsRecipient: result.fundsRecipient,
network: flags.network,
apiUrl: flags.apiUrl,
})
}

private async executeLaunch(params: {
launchType: 'project' | 'memecoin'
name: string
symbol: string
image: string
description?: string
website?: string
twitter?: string
telegram?: string
depositStartTime: string
quoteMint: string
tokenAllocation?: number
raiseGoal?: number
raydiumLiquidityBps?: number
fundsRecipient?: string
lockedAllocationsFile?: string
network?: 'solana-mainnet' | 'solana-devnet'
apiUrl: string
}): Promise<unknown> {
const isMemecoin = params.launchType === 'memecoin'
const spinner = ora('Creating token launch via Genesis API...').start()

try {
// Detect network from chain if not specified
const network: SvmNetwork = flags.network ?? detectSvmNetwork(this.context.chain)
const network: SvmNetwork = params.network ?? detectSvmNetwork(this.context.chain)

// Build external links
const externalLinks: Record<string, string> = {}
if (flags.website) externalLinks.website = flags.website
if (flags.twitter) externalLinks.twitter = flags.twitter
if (flags.telegram) externalLinks.telegram = flags.telegram
if (params.website) externalLinks.website = params.website
if (params.twitter) externalLinks.twitter = params.twitter
if (params.telegram) externalLinks.telegram = params.telegram

// Build token metadata
const wallet = this.context.signer.publicKey.toString()
const token = {
name: flags.name,
symbol: flags.symbol,
image: flags.image,
...(flags.description && { description: flags.description }),
name: params.name,
symbol: params.symbol,
image: params.image,
...(params.description && { description: params.description }),
...(Object.keys(externalLinks).length > 0 && { externalLinks }),
}

let input: CreateLaunchInput
let launchInput: CreateLaunchInput

if (isMemecoin) {
const memecoinInput: CreateMemecoinLaunchInput = {
wallet,
token,
launchType: 'memecoin',
launch: {
depositStartTime: flags.depositStartTime,
depositStartTime: params.depositStartTime,
},
network,
...(flags.quoteMint !== 'SOL' && { quoteMint: flags.quoteMint as QuoteMintInput }),
...(params.quoteMint !== 'SOL' && { quoteMint: params.quoteMint as QuoteMintInput }),
}
input = memecoinInput
launchInput = memecoinInput
} else {
// Parse locked allocations from JSON file if provided
let lockedAllocations: LockedAllocation[] | undefined
if (flags.lockedAllocations) {
lockedAllocations = this.parseLockedAllocations(flags.lockedAllocations)
if (params.lockedAllocationsFile) {
lockedAllocations = this.parseLockedAllocations(params.lockedAllocationsFile)
}

const projectInput: CreateProjectLaunchInput = {
Expand All @@ -203,22 +299,22 @@ Launch types:
launchType: 'project',
launch: {
launchpool: {
tokenAllocation: flags.tokenAllocation!,
depositStartTime: flags.depositStartTime,
raiseGoal: flags.raiseGoal!,
raydiumLiquidityBps: flags.raydiumLiquidityBps!,
fundsRecipient: flags.fundsRecipient!,
tokenAllocation: params.tokenAllocation!,
depositStartTime: params.depositStartTime,
raiseGoal: params.raiseGoal!,
raydiumLiquidityBps: params.raydiumLiquidityBps!,
fundsRecipient: params.fundsRecipient!,
},
...(lockedAllocations && { lockedAllocations }),
},
network,
...(flags.quoteMint !== 'SOL' && { quoteMint: flags.quoteMint as QuoteMintInput }),
...(params.quoteMint !== 'SOL' && { quoteMint: params.quoteMint as QuoteMintInput }),
}
input = projectInput
launchInput = projectInput
}

const apiConfig: GenesisApiConfig = {
baseUrl: flags.apiUrl,
baseUrl: params.apiUrl,
}

spinner.text = 'Building transactions via Genesis API...'
Expand All @@ -231,7 +327,7 @@ Launch types:
const result = await createAndRegisterLaunch(
this.context.umi,
apiConfig,
input,
launchInput,
{ commitment },
)

Expand Down
Loading
Loading