Skip to content
helixLaunch

SDK

Build · TypeScript

Launch from code

ix.createLaunch creates the token, its curve pool and its launch in one instruction. The creator can add a first buy to the same transaction. The examples use read() and send() from the quickstart.

launch.ts
import { generateKeyPairSigner, type Instruction, type KeyPairSigner } from '@solana/kit';
import {
  NO_RULES,
  PRESETS,
  configAddress,
  decodeConfig,
  holdingAddress,
  ix,
  launchAddress,
  poolAddress,
  setComputeUnitLimit,
  type LaunchRules,
} from '@helixfi/sdk';
// read() and send() as in the SDK quickstart.

export async function launchToken(creator: KeyPairSigner, firstBuy: bigint) {
  const config = await configAddress();
  const { feeCollector } = decodeConfig(await read(config));

  // The mint is a fresh keypair: it signs this one transaction and is never needed again.
  const mint = await generateKeyPairSigner();
  const pool = await poolAddress(mint.address);
  const launch = await launchAddress(mint.address);
  const poolHolding = await holdingAddress(mint.address, pool);

  // A preset, or any rules inside BOUNDS.
  const rules: LaunchRules = PRESETS.find((p) => p.id === 'diamond')?.rules ?? { ...NO_RULES, creatorFeeBps: 100 };

  const instructions: Instruction[] = [
    setComputeUnitLimit(400_000),
    ix.createLaunch(
      { creator, config, mint, pool, poolHolding, launch, feeCollector },
      { name: 'Example', symbol: 'EXMPL', uri: 'https://example.com/token.json', rules },
    ),
  ];
  if (firstBuy > 0n) {
    // The creator's first buy pays no sniper fee, once.
    const traderHolding = await holdingAddress(mint.address, creator.address);
    instructions.push(
      ix.createHolding({ payer: creator, holding: traderHolding, mint: mint.address, owner: creator.address }),
      ix.swap(
        { trader: creator, config, mint: mint.address, pool, poolHolding, traderHolding, launch },
        { side: 'buy', amountIn: firstBuy, minOut: 0n },
      ),
    );
  }
  const signature = await send(creator, instructions); // signs with the creator and the mint
  return { signature, mint: mint.address, pool, launch };
}
  • The creator pays the launch fee (0.01 SOL by default) and the rent of the four new accounts. The fee collector must be the one in the config, so read it rather than hard-coding it.
  • The creator's first buy is exempt from the sniper fee (30% at the start by default), once. Putting it in the launch transaction means nobody can buy before it.
  • Name up to 32 bytes, symbol up to 16, URI up to 200; name and symbol can't be empty. The URI points at your metadata JSON.
rules.ts
import { NO_RULES, type LaunchRules } from '@helixfi/sdk';

const DAY = 86_400;

export const myRules: LaunchRules = {
  ...NO_RULES,
  creatorFeeBps: 100, // 1% of every buy and sell, in SOL, to the creator
  holderFeeSellBps: 200, // sellers pay holders 2% in SOL
  burnBuyBps: 50, // 0.5% of the tokens of every buy is burned
  maxWalletBps: 200, // no wallet above 2% of supply until graduation
  creatorLockSecs: 30 * DAY, // the creator can't sell or transfer for 30 days
  earlyWindowSecs: 30, // tokens bought in the first 30 s…
  earlyLockSecs: DAY, // …are locked for a day
};

Anything outside the bounds fails with RuleOutOfBounds: creator fee up to 2%, holder fee up to 3% and burn up to 2% per side, at most 5% for the three together on a side, max wallet off or 0.5% to 5%, and an early window with its lock, or neither. The rules are fixed once the launch exists. Each rule is described on Launches.

The launch mints the supply through the hook in its own instruction, so the hook must be set up for the mint's address first. The mint is a fresh keypair, so its address is known before it exists. This is the flow of packages/sdk/test/hook.test.ts.

launch-with-hook.ts
import { generateKeyPairSigner, type Address, type KeyPairSigner } from '@solana/kit';
import {
  EXAMPLE_HOOK_PROGRAM_ID,
  HOOK_FLAGS,
  NO_RULES,
  configAddress,
  exampleHookConfigAddress,
  exampleHookExtras,
  holdingAddress,
  hookAccounts,
  ix,
  launchAddress,
  poolAddress,
  swapHookAccounts,
} from '@helixfi/sdk';

export async function launchWithHook(creator: KeyPairSigner, collector: Address, feeCollector: Address) {
  const hookProgram = EXAMPLE_HOOK_PROGRAM_ID;
  const flags = HOOK_FLAGS.BEFORE_TRANSFER | HOOK_FLAGS.TRANSFER_RETURNS_DELTA | HOOK_FLAGS.WRITES_HOOK_DATA;
  const mint = await generateKeyPairSigner();
  const config = await configAddress();
  const pool = await poolAddress(mint.address);
  const launch = await launchAddress(mint.address);
  const poolHolding = await holdingAddress(mint.address, pool);
  const collectorHolding = await holdingAddress(mint.address, collector);

  // 1. Prepare the hook for the address the mint will have (here: a 3% cut on trades).
  const hookConfig = await exampleHookConfigAddress(hookProgram, mint.address);
  await send(creator, [
    ix.exampleHookInit(
      { payer: creator, config: hookConfig, mint: mint.address, hookProgram },
      { feeBps: 300, collectorOwner: collector, collectorHolding },
    ),
  ]);

  // 2. Launch with it. The launch mints the supply through the hook, so it takes the hook's accounts.
  const extras = await exampleHookExtras(hookProgram, mint.address, collectorHolding);
  const hook = await hookAccounts(mint.address, { hookProgram }, extras);
  await send(creator, [
    ix.createLaunch(
      { creator, config, mint, pool, poolHolding, launch, feeCollector, hook },
      { name: 'Taxed', symbol: 'TAX', uri: '', rules: { ...NO_RULES, creatorFeeBps: 50 }, customHook: hookProgram, customHookFlags: flags },
    ),
    // 3. The holding that receives the cuts must exist before the first trade.
    ix.createHolding({ payer: creator, holding: collectorHolding, mint: mint.address, owner: collector }),
  ]);

  // Every swap of this token now passes the hook's accounts.
  return swapHookAccounts({ hookProgram }, extras);
}
claims.ts
import { type Address, type KeyPairSigner } from '@solana/kit';
import { claimableRewards, decodeHolding, decodeLaunch, holdingAddress, ix, launchAddress } from '@helixfi/sdk';

export async function claims(creator: KeyPairSigner, holder: KeyPairSigner, fan: KeyPairSigner, mint: Address) {
  const launch = await launchAddress(mint);
  const holding = await holdingAddress(mint, holder.address);

  // What a holder can claim right now, computed like the program does.
  const now = BigInt(Math.floor(Date.now() / 1000));
  const owed = claimableRewards(decodeLaunch(await read(launch)), decodeHolding(await read(holding)), now);

  return {
    owed,
    creator: ix.claimCreatorFees({ creator, launch }), // all unclaimed creator fees, in SOL
    holder: ix.claimRewards({ owner: holder, mint, launch, holding }), // the holder's rewards, in SOL
    share: ix.shareWithHolders({ payer: fan, launch }, 1_000_000_000n), // 1 SOL to holders, streamed over an hour
  };
}

claimableRewards repeats the kit's bookkeeping off chain, including a share still streaming. A share must be at least 0.001 SOL and needs a launch with holder rewards. None of these can be paused.