Developers
Create tokens on Livo programmatically
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 LivoFactoryUniV2UnifiedContract 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-datatxHashrequirednamerequiredsymbolrequiredchainIdrequireddescriptionsocialsimageResponse:
{
"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 4Validation Rules
namesymbolbuyTaxBpssellTaxBpstaxDurationSecondsfeeSplitSharesKey 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