Developers

Create tokens on Livo programmatically

New factories are announced in the Livo dev integration updates Telegram group — join to stay up to date.

Overview

Token creation on Livo follows a strict order: build the create-token transaction and precompute its hash, submit the token metadata (name, image, socials) to the Livo API keyed by that precomputed hash, and finally broadcast the signed transaction on-chain.

Order matters. The metadata submission and the on-chain broadcast must be performed one after the other with no delay between them.

Step 1: Authenticate

All API calls require a JWT Bearer token. Obtain one by signing a message with your wallet. Tokens expire after 7 days.

POST /api/auth/wallet
Content-Type: application/json

{
  "address": "0xYourWalletAddress",
  "signature": "<signature>",
  "message": "Sign in to Livo\nTimestamp: <unix_ms>"
}

// Response:
{ "token": "eyJhbGci..." }

The message must contain Timestamp: <unix_ms> where the timestamp is within the last 5 minutes.

Step 2: Build the Create Token Call

⚠ Updating — this section reflects the legacy 6-factory model. The protocol now uses two unified factories (V2 / V4) selected purely by creatorLPFeeShare. Each accepts a unified createToken taking FeeShare[], SupplyShare[], TaxConfigInit and AntiSniperConfigs. Updated docs land alongside the next contract release.

if (creatorLPFeeShare):
    use LivoFactoryUniV4Unified
else:
    use LivoFactoryUniV2Unified

Contract addresses are maintained in the contracts repo. All contracts are verified on Etherscan — fetch ABIs from there.

Function Signatures

createToken (no tax)

function createToken(
    string name,
    string symbol,
    address feeReceiver,  // usually msg.sender
    bytes32 salt          // vanity salt (address must end in 1110)
) returns (address token)

createToken (with tax)

function createToken(
    string name,
    string symbol,
    address feeReceiver,
    bytes32 salt,
    uint16 buyTaxBps,           // max 500 (5%)
    uint16 sellTaxBps,          // max 500 (5%)
    uint32 taxDurationSeconds   // max 1209600 (14 days)
) returns (address token)

createTokenWithFeeSplit (multiple fee recipients)

function createTokenWithFeeSplit(
    string name,
    string symbol,
    address[] recipients,
    uint256[] sharesBps,  // must sum to 10000
    bytes32 salt
) returns (address token, address feeSplitter)

Vanity Salt

The factory uses CREATE2. Livo requires the deployed token address to end in 1110. You must brute-force a salt that produces a matching address.

import { keccak256, concat, toBytes } from "viem";

const PROXY_PREFIX = "0x3d602d80600a3d3981f3363d3d373d3d3d363d73";
const PROXY_SUFFIX = "0x5af43d82803e903d91602b57fd5bf3";

function findVanitySalt(factoryAddress, tokenImplementation) {
  const initcodeHash = keccak256(
    concat([PROXY_PREFIX, tokenImplementation, PROXY_SUFFIX])
  );
  const buffer = new Uint8Array(85);
  buffer[0] = 0xff;
  buffer.set(toBytes(factoryAddress), 1);
  buffer.set(toBytes(initcodeHash), 53);

  const salt = crypto.getRandomValues(new Uint8Array(32));
  for (;;) {
    buffer.set(salt, 21);
    const hash = keccak256(buffer);
    if (hash.endsWith("1110")) {
      const saltHex = "0x" + Array.from(salt,
        (b) => b.toString(16).padStart(2, "0")).join("");
      return { salt: saltHex, tokenAddress: "0x" + hash.slice(26) };
    }
    for (let i = 31; i >= 0; i--) {
      if (salt[i] < 255) { salt[i]++; break; }
      salt[i] = 0;
    }
  }
}

Step 3: Precompute the Transaction Hash

Encode the createToken call, build an EIP-1559 transaction (chainId, nonce, gas, fee fields, data, value=0), sign it offline, and take the keccak256 of the signed RLP payload. That digest is the txHash you submit to the API in Step 4 — and the same hash the network will assign once you broadcast the transaction in Step 5.

import { keccak256, encodeFunctionData } from "viem";

const data = encodeFunctionData({
  abi: factoryAbi,
  functionName: "createToken",
  args: [name, symbol, feeReceiver, salt],
});

const fees = await publicClient.estimateFeesPerGas();
const tx = {
  type: "eip1559",
  chainId,
  nonce: await publicClient.getTransactionCount({ address: account.address }),
  to: factoryAddress,
  data,
  value: 0n,
  gas: await publicClient.estimateGas({ account, to: factoryAddress, data }),
  maxFeePerGas: fees.maxFeePerGas,
  maxPriorityFeePerGas: fees.maxPriorityFeePerGas,
};

const signedTx = await walletClient.signTransaction(tx);
const txHash = keccak256(signedTx);

Step 4: Submit Metadata

Submit token metadata to the API before broadcasting the transaction, using the precomputed txHash from Step 3.

POST /api/tokens/create
Authorization: Bearer <jwt_token>
Content-Type: multipart/form-data
txHashrequired
0x-prefixed, 66-character hex string (precomputed in Step 3)
namerequired
Token name, max 32 characters
symbolrequired
Max 15 characters
chainIdrequired
1 (mainnet) or 11155111 (sepolia)
description
Max 250 characters
socials
JSON array of up to 5 http(s) URL strings (e.g. ["https://x.com/foo","https://t.me/bar"]). Icons are derived from each URL; invalid entries are dropped.
image
JPEG, PNG, GIF, or WebP, max 5MB

Response:

{
  "success": true,
  "txHash": "0x...",
  "imageUrl": "https://..."
}

Step 5: Broadcast the Transaction

As soon as the metadata POST returns successfully, broadcast the signed transaction from Step 3. Steps 4 and 5 must be performed back-to-back with no delay between them.

const hash = await publicClient.sendRawTransaction({
  serializedTransaction: signedTx,
});
// hash === txHash submitted in Step 4

Validation Rules

name
1-32 characters, non-empty
symbol
1-15 characters
buyTaxBps
0-500 (0%-5%)
sellTaxBps
0-500 (0%-5%)
taxDurationSeconds
60-1209600 (1m-14 days)
feeSplitShares
Must sum to 10000, no duplicate addresses

Key Events

After token creation, the factory emits:

event TokenCreated(
    address indexed token,
    string name,
    string symbol,
    address tokenOwner,
    address launchpad,
    address graduator,
    address feeHandler,
    address feeReceiver
)

The launchpad emits:

event TokenLaunched(
    address indexed token,
    uint256 graduationThreshold,
    uint256 maxExcessOverThreshold
)

Parse the TokenLaunched event from the transaction receipt to get the created token address.

Notes

  • Token creation is free (no ETH cost beyond gas).
  • Tokens start on a bonding curve. Once ~3.5 ETH is collected, the token automatically graduates to Uniswap.
  • The image field should be a raw file upload (not a URL). The API uploads it to IPFS via Pinata.

Complete Example

End-to-end token creation using viem (no tax, single fee receiver). Assumes findVanitySalt from the Vanity Salt section and a factoryAbi fetched from Etherscan.

import {
  createPublicClient,
  createWalletClient,
  http,
  keccak256,
  encodeFunctionData,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";

const API_BASE = "https://livo.pro";
const FACTORY_ADDRESS = "0x..."; // factoryV2 — see contracts repo
const TOKEN_IMPL = "0x...";      // token implementation, for vanity salt

const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const publicClient = createPublicClient({ chain: mainnet, transport: http() });
const walletClient = createWalletClient({ account, chain: mainnet, transport: http() });

// 1. Authenticate
const timestamp = Date.now();
const message = `Sign in to Livo\nTimestamp: ${timestamp}`;
const signature = await walletClient.signMessage({ account, message });
const { token: jwt } = await fetch(`${API_BASE}/api/auth/wallet`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ address: account.address, signature, message }),
}).then((r) => r.json());

// 2. Build the createToken call data
const name = "My Token";
const symbol = "MTK";
const { salt } = findVanitySalt(FACTORY_ADDRESS, TOKEN_IMPL);
const data = encodeFunctionData({
  abi: factoryAbi,
  functionName: "createToken",
  args: [name, symbol, account.address, salt],
});

// 3. Sign the transaction offline and precompute the txHash
const fees = await publicClient.estimateFeesPerGas();
const tx = {
  type: "eip1559",
  chainId: mainnet.id,
  nonce: await publicClient.getTransactionCount({ address: account.address }),
  to: FACTORY_ADDRESS,
  data,
  value: 0n,
  gas: await publicClient.estimateGas({ account, to: FACTORY_ADDRESS, data }),
  maxFeePerGas: fees.maxFeePerGas,
  maxPriorityFeePerGas: fees.maxPriorityFeePerGas,
};
const signedTx = await walletClient.signTransaction(tx);
const txHash = keccak256(signedTx);

// 4. Submit metadata BEFORE broadcasting (must be immediately followed by step 5)
const form = new FormData();
form.append("txHash", txHash);
form.append("name", name);
form.append("symbol", symbol);
form.append("chainId", String(mainnet.id));
form.append("description", "An example token");
// Socials: JSON array of up to 5 http(s) URLs. The icon is derived from each
// URL; invalid entries are dropped server-side.
form.append("socials", JSON.stringify([
  "https://x.com/yourproject",
  "https://t.me/yourproject",
]));
// form.append("image", imageFile); // optional File / Blob
const metaRes = await fetch(`${API_BASE}/api/tokens/create`, {
  method: "POST",
  headers: { Authorization: `Bearer ${jwt}` },
  body: form,
});
if (!metaRes.ok) throw new Error(`metadata POST failed: ${await metaRes.text()}`);

// 5. Broadcast immediately — no delay allowed between steps 4 and 5
const broadcastedHash = await publicClient.sendRawTransaction({
  serializedTransaction: signedTx,
});
console.log("token tx:", broadcastedHash); // === txHash