Klik Finance<- Back to app
Developer Docs
Overview
What is KlikFee ScheduleFactory Contracts
Launch
Launch SequenceClaim FeesFull Examples
APIs
Atomic EndpointUpload ImageUpload MetadataGenerate SaltRate Limits
Klik Finance

Developer Documentation

Last Updated: April 26, 2026

What is Klik?

Klik is a permissionless token launchpad on Ethereum mainnet and Base. Anyone can deploy a fully on-chain ERC-20 token with bootstrapped Uniswap V4 liquidity in a single transaction - no seed rounds, no VCs, no presales.

Every token launched through Klik:

  • Gets a vanity address starting with 69 (Ethereum) or 420 (Base), mined via CREATE2
  • Launches with real Uniswap V4 liquidity locked in the pool
  • Is immediately tradable with swap fees flowing to the creator and the platform

Note: In some cases, a token address might not start with 0x69 (or 0x420 on Base), and that is not an issue.

Fee Schedule

Klik charges a dynamic swap fee that decreases as the token's market cap grows. This rewards early believers and reduces friction for larger tokens. Fees are split between the platform and the token creator.

Market Cap (ETH)Total FeePlatformCreator
0 - 15 ETH1.00%0.60%0.40%
15 - 25 ETH0.95%0.55%0.40%
25 - 50 ETH0.90%0.45%0.45%
50 - 100 ETH0.85%0.42%0.43%
100 - 200 ETH0.80%0.38%0.42%
200 - 350 ETH0.75%0.35%0.40%
350 - 550 ETH0.70%0.32%0.38%
550 - 800 ETH0.65%0.29%0.36%
800 - 1200 ETH0.62%0.26%0.36%
1200 - 1600 ETH0.58%0.24%0.34%
1600 - 2000 ETH0.54%0.22%0.32%
2000 - 3000 ETH0.50%0.20%0.30%
3000 - 4500 ETH0.35%0.13%0.22%
4500 - 6000 ETH0.22%0.07%0.15%
>= 6000 ETH0.10%0.03%0.07%

Factory Contracts

Pass the correct factory address for your target chain when calling deployCoin or generating a salt.

ChainChain IDFactory Address
Ethereum10x254Bf550657040f78608476cE9AaD820aB2266ad
Base84530xB6CB1c049ee8942683Fd3172f7EBA63B6e8a6835

Launch Sequence

deployCoin is the only on-chain step. Everything before it - hosting metadata, picking a salt - is up to you. The Klik APIs exist for convenience but are not required: any URL-hosted JSON and any random bytes32 work. Each step below explains the API path the in-app launch flow uses, plus the self-hosted alternative.

Step 1 - Image (use any host you like)

Your token needs an image URL inside its metadata JSON. You do not need to call our API for this - host the image on your own CDN, S3, R2, IPFS pin, anywhere reachable over HTTP(S), and reference that URL in the metadata JSON. The Klik in-app flow uses /api/uploadImage only as a convenience for users who don't have their own hosting; it's not part of the deploy contract.

Step 2 - Metadata JSON (use any host you like)

deployCoin takes a string metadataUri. The contract stores it verbatim and never fetches it. You do not need to use our metadata API - any URL pointing at JSON in the structure below will work: an HTTPS URL on your own server (e.g. https://example.com/metadata.json), an ipfs://... URI, an Arweave link, anything. Klik and any third-party indexer that follows our schema will read it.

Required JSON structure:

{
  "name": "My Token",
  "symbol": "MYT",
  "description": "Your token description",
  "image": "https://example.com/logo.png",
  "website": "https://mytoken.com",
  "twitter": "https://x.com/mytoken",
  "telegram": "https://t.me/mytoken"
}

The Klik in-app flow uses /api/uploadMetadata to pin the JSON to IPFS for users who don't want to run their own host. It's a convenience layer - the contract treats your self-hosted URL identically.

Step 3 - Salt (use any random bytes32)

deployCoin takes a bytes32 salt that determines the CREATE2 address of your token. You do not need to call our API for this - any random 32-byte value works, generated client-side in your frontend or backend:

// Random bytes32 (browser)
const salt = '0x' + Array.from(crypto.getRandomValues(new Uint8Array(32)))
  .map(b => b.toString(16).padStart(2, '0'))
  .join('');

// Random bytes32 (Node / ethers)
const salt = ethers.hexlify(ethers.randomBytes(32));

Use /api/generate-salt only if you want a vanity address starting with 0x69 (Ethereum) or 0x420 (Base). The API mines salts on a private RPC pool until it finds one that produces the desired prefix. Skipping the call costs nothing - your token still deploys, just at a non-vanity address.

Step 4 - Call deployCoin

function deployCoin(
  string calldata name,
  string calldata symbol,
  string calldata metadataUri,
  bytes32 salt,
  uint256 configId
) external payable;
  • metadataUri - any URL pointing at JSON in the structure above (the in-app flow passes the IPFS CID returned by /api/uploadMetadata, but https://example.com/metadata.json works just as well)
  • salt - any 0x-prefixed bytes32. Use /api/generate-salt only if you want a vanity prefix; otherwise random bytes are fine
  • configId - Ethereum uses 0 to 4; Base uses 0
  • msg.value - optional creator buy amount

Ethereum liquidity tiers:

configIdStarting Liquidity
00.69 ETH
11 ETH
22 ETH
35 ETH
410 ETH

Base: only configId = 0 is available.

Creator Buy Penalty

⚠️ Removed — the creator buy penalty no longer exists. Creator buys at deploy time are no longer penalized.

If you pass a non-zero msg.value to deployCoin, the contract applies a penalty to discourage disproportionate supply accumulation by the creator at launch. The penalty is not a flat percentage — each liquidity config carries its ownpenaltyMultiplier, so the same creator buy amount results in a different penalty depending on which configId you picked.

The effective penalty is derived on-chain from two values:

  • getPenalty(ethAmount) — the base penalty for your buy size (in bps)
  • getLiquidityConfig(configId).penaltyMultiplier — a per-config scaling factor

Final penalty ≈ getPenalty(msg.value) × penaltyMultiplier. Higher-liquidity tiers tend to carry a lower multiplier, so a creator buy on a deeper pool is penalized less than the same buy on a shallower pool. Always simulate deployCoin with your intended msg.value before sending the transaction — the simulation returns the exact token amount you would receive, inclusive of penalty, so you can confirm the outcome instead of deploying blind.

Claim Fees

Every swap on a Klik V4 pool routes a slice of ETH through the hook. The hook sends the platform share straight to the platform treasury and parks the creator share inside the token contract itself. To withdraw that creator share to your wallet, the creator (or the platform admin) makes a single on-chain call:

function collectFees(address tokenAddress)
  external
  returns (uint256 ethCollected);
  • Call it on the factory, not the token. Factory addresses are listed under Factory Contracts.
  • The caller must be either the token's creator (the address recorded as creator on the token contract) or the platform admin. Anyone else gets "Not authorized".
  • The factory withdraws the token contract's entire ETH balance and forwards it to the creator address in the same transaction.
  • The platform share was already taken atomically at swap time, so 100% of the balance held by the token contract belongs to the creator - no extra split happens here.
  • If there's nothing to claim the call simply returns 0 - no revert. Fee balance is per-token, so you call once per token you want to claim.

This applies to the new V4 factory only. V3-legacy tokens use a different flow (Uniswap NFT positions); not covered here.

JavaScript (ethers)

import { ethers } from 'ethers';

const FACTORY_ADDRESS = '0x254Bf550657040f78608476cE9AaD820aB2266ad'; // Ethereum
const TOKEN_ADDRESS   = '0xYourKlikTokenAddress';

const FACTORY_ABI = [
  'function collectFees(address tokenAddress) returns (uint256 ethCollected)'
];

const provider = new ethers.JsonRpcProvider('https://rpc.mevblocker.io/fast');
const wallet   = new ethers.Wallet('0xyour_creator_private_key', provider);

const factory = new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, wallet);

const tx = await factory.collectFees(TOKEN_ADDRESS);
console.log('Broadcasted:', tx.hash);
const receipt = await tx.wait();
console.log('Confirmed in block:', receipt.blockNumber);

Python (web3.py)

from web3 import Web3

FACTORY_ADDRESS = '0x254Bf550657040f78608476cE9AaD820aB2266ad'
TOKEN_ADDRESS   = '0xYourKlikTokenAddress'

FACTORY_ABI = [
    {
        'type': 'function',
        'name': 'collectFees',
        'stateMutability': 'nonpayable',
        'inputs': [{'name': 'tokenAddress', 'type': 'address'}],
        'outputs': [{'name': 'ethCollected', 'type': 'uint256'}],
    }
]

w3 = Web3(Web3.HTTPProvider('https://rpc.mevblocker.io/fast'))
account = w3.eth.account.from_key('0xyour_creator_private_key')

factory = w3.eth.contract(
    address=Web3.to_checksum_address(FACTORY_ADDRESS),
    abi=FACTORY_ABI,
)

tx = factory.functions.collectFees(
    Web3.to_checksum_address(TOKEN_ADDRESS)
).build_transaction({
    'from': account.address,
    'nonce': w3.eth.get_transaction_count(account.address),
})

signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
print('Broadcasted:', tx_hash.hex())

receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print('Confirmed in block:', receipt.blockNumber)

How much can I claim right now?

The token contract holds the unclaimed creator share as plain ETH balance. Read it without sending a tx:

const claimable = await provider.getBalance(TOKEN_ADDRESS);
console.log('Claimable creator ETH:', ethers.formatEther(claimable));

That value is exactly what collectFees would forward to the creator if called right now.

Atomic Endpoint

Base URL: https://klik.finance

/api/uploadTokenMetadata is the one-call developer flow. It is not the path used by the in-app launch modal, but it is useful for external integrations.

Content-Type: multipart/form-data or application/json

Use multipart when sending an image. JSON mode is text-only.

FieldTypeRequiredDescription
namestringYesToken name
symbolstringYesToken ticker symbol
descriptionstringNoToken description
websitestringNoProject website URL
twitterstringNoTwitter/X handle or URL
telegramstringNoTelegram link
imagefileNoToken image, multipart only, max 1 MB
creatorstringNoWallet address, required if you want salt generation
factorystringNoFactory address, defaults to Ethereum
chainIdstringNo"1" or "8453"
testnetbooleanNoUse testnet RPC for salt generation
Example payload (multipart with image)
{
  "name": "My Token",
  "symbol": "MYT",
  "description": "Your token description",
  "creator": "0xYourWalletAddress",
  "chainId": "1",
  "image": "<binary file>"
}
Example payload (JSON without image)
{
  "name": "My Token",
  "symbol": "MYT",
  "description": "Your token description",
  "creator": "0xYourWalletAddress",
  "chainId": "1"
}

Response:

{
  "cid": "QmXyz...",
  "url": "QmXyz...",
  "imageCid": "QmAbc...",
  "salt": "0xdeadbeef...",
  "saltAddress": "0x69ab12...",
  "targetPrefix": "69",
  "hasTargetPrefix": true
}

Upload Image

Endpoint: POST /api/uploadImage

Content-Type: multipart/form-data

FieldTypeRequiredDescription
filefileYesImage file, max 1 MB
Frontend payload
{
  "file": "<binary file>"
}

Response:

{ "cid": "QmAbc...", "url": "QmAbc..." }

Upload Metadata

Endpoint: POST /api/uploadMetadata

Content-Type: application/json

The frontend sends this payload before launch.

Frontend payload
{
  "name": "My Token",
  "symbol": "MYT",
  "description": "Your token description",
  "image": "ipfs://QmImageCid...",
  "website": "https://mytoken.com",
  "twitter": "https://x.com/mytoken",
  "telegram": "https://t.me/mytoken"
}

Example payload:

{
  "name": "My Token",
  "symbol": "MYT",
  "description": "Your token description",
  "image": "ipfs://QmAbc...",
  "website": "https://mytoken.com",
  "twitter": "https://x.com/mytoken",
  "telegram": "https://t.me/mytoken"
}

Response:

{ "cid": "QmXyz...", "url": "QmXyz..." }

Generate Salt

Endpoint: POST /api/generate-salt

The frontend sends the chain-specific factory address along with the wallet address.

FieldTypeRequiredDescription
namestringYesToken name
symbolstringYesToken symbol
creatorstringYesCreator wallet address
factorystringNoFactory address, defaults to Ethereum factory
chainIdstringNo"1" or "8453"
testnetbooleanNoUse testnet RPC
num_matchesnumberNoDefaults to 1
max_attemptsnumberNoDefaults to 25000
Frontend payload
{
  "name": "My Token",
  "symbol": "MYT",
  "creator": "0xYourWalletAddress",
  "factory": "0x254Bf550657040f78608476cE9AaD820aB2266ad",
  "chainId": "1"
}

Response:

{
  "factory": "0x254Bf550657040f78608476cE9AaD820aB2266ad",
  "target_prefix": "69",
  "total_attempts": 4821,
  "has_target_prefix": true,
  "results": [
    { "salt": "0xdeadbeef...", "address": "0x69ab12..." }
  ]
}

If has_target_prefix is false, the endpoint still returns a valid fallback salt.

Rate Limits

All endpoints enforce per-IP rate limits.

EndpointPer SecondPer Minute
/api/uploadImage30120
/api/uploadMetadata30120
/api/uploadTokenMetadata1060
/api/generate-salt-60
/api/search6100
/api/sort-mcap640
/api/sort-trending430
/api/deployed-tokens420
/api/ecosystem-stats420
{
  "error": "Rate limit exceeded",
  "message": "Too many requests per second. Please slow down.",
  "resetIn": 1,
  "type": "second"
}

Full Examples

These examples match the current app flow more closely than the previous version: metadata upload, salt generation, then deployCoin.

JavaScript (ethers)

Install: npm install ethers

import { ethers } from 'ethers';

const FACTORY_ABI = [
  'function deployCoin(string name, string symbol, string metadataUri, bytes32 salt, uint256 configId) payable'
];

const RPC_URL = 'https://rpc.mevblocker.io/fast';
const PRIVATE_KEY = '0xyour_private_key_here';
const CHAIN_ID = '1';
const FACTORY_ADDRESS = '0x254Bf550657040f78608476cE9AaD820aB2266ad';

const TOKEN_NAME = 'My Token';
const TOKEN_SYMBOL = 'MYT';
const TOKEN_DESCRIPTION = 'Your token description';
const CONFIG_ID = 1n;
const CREATOR_BUY_ETH = '0';

async function main() {
  const provider = new ethers.JsonRpcProvider(RPC_URL);
  const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
  const creator = await wallet.getAddress();

  const metadataRes = await fetch('https://klik.finance/api/uploadMetadata', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      name: TOKEN_NAME,
      symbol: TOKEN_SYMBOL,
      description: TOKEN_DESCRIPTION,
      image: '',
      website: '',
      twitter: '',
      telegram: '',
    }),
  });
  if (!metadataRes.ok) throw new Error(await metadataRes.text());
  const { cid } = await metadataRes.json();

  const saltRes = await fetch('https://klik.finance/api/generate-salt', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      name: TOKEN_NAME,
      symbol: TOKEN_SYMBOL,
      creator,
      factory: FACTORY_ADDRESS,
      chainId: CHAIN_ID,
    }),
  });
  if (!saltRes.ok) throw new Error(await saltRes.text());
  const saltPayload = await saltRes.json();
  const salt = saltPayload.results[0].salt;

  const factory = new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, wallet);
  const tx = await factory.deployCoin(
    TOKEN_NAME,
    TOKEN_SYMBOL,
    cid,
    salt,
    CONFIG_ID,
    { value: ethers.parseEther(CREATOR_BUY_ETH) }
  );

  console.log('Broadcasted tx:', tx.hash);
  const receipt = await tx.wait();
  console.log('Confirmed in block:', receipt.blockNumber);
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

Python (web3.py)

Install: pip install web3 requests

import requests
from web3 import Web3

FACTORY_ABI = [
    {
        "type": "function",
        "name": "deployCoin",
        "stateMutability": "payable",
        "inputs": [
            {"name": "name", "type": "string"},
            {"name": "symbol", "type": "string"},
            {"name": "metadataUri", "type": "string"},
            {"name": "salt", "type": "bytes32"},
            {"name": "configId", "type": "uint256"}
        ],
        "outputs": []
    }
]

RPC_URL = "https://rpc.mevblocker.io/fast"
PRIVATE_KEY = "0xyour_private_key_here"
CHAIN_ID = "1"
FACTORY_ADDRESS = "0x254Bf550657040f78608476cE9AaD820aB2266ad"

TOKEN_NAME = "My Token"
TOKEN_SYMBOL = "MYT"
TOKEN_DESCRIPTION = "Your token description"
CONFIG_ID = 1
CREATOR_BUY_ETH = "0"

w3 = Web3(Web3.HTTPProvider(RPC_URL))
account = w3.eth.account.from_key(PRIVATE_KEY)
creator = account.address

metadata_res = requests.post(
    "https://klik.finance/api/uploadMetadata",
    json={
        "name": TOKEN_NAME,
        "symbol": TOKEN_SYMBOL,
        "description": TOKEN_DESCRIPTION,
        "image": "",
        "website": "",
        "twitter": "",
        "telegram": "",
    },
    timeout=60,
)
metadata_res.raise_for_status()
cid = metadata_res.json()["cid"]

salt_res = requests.post(
    "https://klik.finance/api/generate-salt",
    json={
        "name": TOKEN_NAME,
        "symbol": TOKEN_SYMBOL,
        "creator": creator,
        "factory": FACTORY_ADDRESS,
        "chainId": CHAIN_ID,
    },
    timeout=60,
)
salt_res.raise_for_status()
salt = salt_res.json()["results"][0]["salt"]

factory = w3.eth.contract(
    address=Web3.to_checksum_address(FACTORY_ADDRESS),
    abi=FACTORY_ABI,
)

deploy_call = factory.functions.deployCoin(
    TOKEN_NAME,
    TOKEN_SYMBOL,
    cid,
    bytes.fromhex(salt[2:]),
    CONFIG_ID,
)

value_wei = w3.to_wei(CREATOR_BUY_ETH, "ether")
tx = deploy_call.build_transaction(
    {
        "from": creator,
        "chainId": int(CHAIN_ID),
        "nonce": w3.eth.get_transaction_count(creator),
        "value": value_wei,
    }
)
tx["gas"] = deploy_call.estimate_gas({"from": creator, "value": value_wei})

latest_block = w3.eth.get_block("latest")
if latest_block.get("baseFeePerGas") is not None:
    tx["maxPriorityFeePerGas"] = w3.to_wei(1, "gwei")
    tx["maxFeePerGas"] = latest_block["baseFeePerGas"] * 2 + tx["maxPriorityFeePerGas"]
else:
    tx["gasPrice"] = w3.eth.gas_price

signed_tx = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
print("Broadcasted tx:", tx_hash.hex())

receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print("Confirmed in block:", receipt.blockNumber)