Documentation
Quickstart
Launch a token with an opening buy, trade it, and collect the fees, with viem on Elysium mainnet. Each step continues from the one before, so the blocks run as a single script.
1 · Set up#
Download the ABIs from the ABI page, then point a client at Elysium. The chain is not yet in viem's built-in list, so define it yourself. The RPC below is the Tartarus proxy; the network page explains why it is the one to use.
import { createPublicClient, createWalletClient, defineChain, http, keccak256, maxUint256, parseAbi, parseEther, toBytes, encodeAbiParameters, parseAbiParameters, zeroAddress } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import factoryAbi from "./TartarusLaunchFactory.json";
import launchAndBuyAbi from "./TartarusLaunchAndBuy.json";
import hookAbi from "./TartarusMemeHook.json";
import escrowAbi from "./TartarusFeeEscrow.json";
const elysium = defineChain({
id: 99901,
name: "Elysium",
nativeCurrency: { name: "HYPE", symbol: "HYPE", decimals: 18 },
rpcUrls: { default: { http: ["https://api.tartarus.world/v1/rpc"] } },
});
const FACTORY = "0x765f2ce211050ec8280257d76072cAa480D75A00";
const LAUNCH_AND_BUY = "0x044a835456057915028DDdFfa161DeA9d4d40615";
const HOOK = "0x00b2Ff65E61aD8c57F957624EDc6C81908Ade044";
const ESCROW = "0x44D1065fD59280cFa26B7BE3dBf13F364314616F";
const ROUTER = "0x1bd4d69bfb8927a045f20518a08390cee3913b0d";
const QUOTER = "0x379f556c5a748bd7cc9baf0f7a344094ac1814f5";
const PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const client = createPublicClient({ chain: elysium, transport: http() });
const wallet = createWalletClient({ account, chain: elysium, transport: http() });
async function send(request: Parameters<typeof wallet.writeContract>[0]) {
const hash = await wallet.writeContract(request);
const receipt = await client.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") throw new Error(`reverted: ${hash}`);
return receipt;
}You need a little HYPE on Elysium for gas and the launch fee. See Bridging HYPE.
2 · Read the terms#
Check that launching is open, then read the fee and pin the terms you are agreeing to.
const CONFIG_ID = 0n; // config 0: native HYPE
const creatorFeeBps = 100; // 1% to you, on top of the protocol's 0.5%
const canLaunch = await client.readContract({
address: FACTORY, abi: factoryAbi, functionName: "canLaunch", args: [account.address],
});
if (!canLaunch) throw new Error("launching is not open for this address");
const launchFee = await client.readContract({
address: FACTORY, abi: factoryAbi, functionName: "launchFee",
}) as bigint;
const expectedEconomics = await client.readContract({
address: FACTORY, abi: factoryAbi, functionName: "previewLaunchEconomics",
args: [CONFIG_ID, creatorFeeBps],
}) as `0x${string}`;expectedEconomics is a digest of the price, fee rates and launch fee. If the owner retunes any of
them before your transaction lands, the launch reverts with LaunchEconomicsMismatch rather than
landing on terms you never saw. Read it in the same flow as the send, and never cache it.
3 · Launch with an opening buy#
The launch fee and the buy both travel as msg.value, so nothing needs approving.
const params = {
name: "My Token",
symbol: "MINE", // unique across Tartarus: [A-Z0-9], 1 to 6 characters
logo: "",
description: "",
socials: { twitter: "", telegram: "", discord: "", website: "", farcaster: "" },
creatorFeeRecipient: zeroAddress, // zero means you
creatorFeeBps,
expectedEconomics,
salt: keccak256(toBytes(crypto.randomUUID())),
};
const openingBuy = parseEther("0.1");
const { result, request } = await client.simulateContract({
account,
address: LAUNCH_AND_BUY, abi: launchAndBuyAbi, functionName: "launchAndBuy",
args: [params, CONFIG_ID, openingBuy, 0n, account.address],
value: launchFee + openingBuy,
});
const [token, tokensOut] = result as [`0x${string}`, bigint];
await send(request);minTokensOut can be 0n here because nobody can trade ahead of you: the pool does not exist until
this transaction creates it, and the buy happens inside the same transaction.
To launch with no opening buy, call factory.launchToken(params, CONFIG_ID) with value: launchFee.
The creator then starts with none of the supply.
4 · Buy#
Every token has the same shape of pool. Read its key from the factory, quote through the real hook, and swap through UniversalRouter.
const quoterAbi = parseAbi([
"function quoteExactInputSingle(((address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) poolKey, bool zeroForOne, uint128 exactAmount, bytes hookData) params) returns (uint256 amountOut, uint256 gasEstimate)",
]);
const routerAbi = parseAbi(["function execute(bytes commands, bytes[] inputs, uint256 deadline) payable"]);
const key = await client.readContract({
address: FACTORY, abi: factoryAbi, functionName: "poolKeyFor", args: [token],
}) as { currency0: `0x${string}`; currency1: `0x${string}`; fee: number; tickSpacing: number; hooks: `0x${string}` };
const quote = async (zeroForOne: boolean, exactAmount: bigint) => {
const { result } = await client.simulateContract({
address: QUOTER, abi: quoterAbi, functionName: "quoteExactInputSingle",
args: [{ poolKey: key, zeroForOne, exactAmount, hookData: "0x" }],
});
return result[0];
};
// V4_SWAP running SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_ALL.
const swap = async (zeroForOne: boolean, amountIn: bigint, minOut: bigint, value: bigint) => {
const [input, output] = zeroForOne ? [key.currency0, key.currency1] : [key.currency1, key.currency0];
const params = encodeAbiParameters(
parseAbiParameters("((address,address,uint24,int24,address),bool,uint128,uint128,uint256,bytes)"),
[[[key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks], zeroForOne, amountIn, minOut, 0n, "0x"]],
);
const settle = encodeAbiParameters(parseAbiParameters("address, uint256"), [input, amountIn]);
const take = encodeAbiParameters(parseAbiParameters("address, uint256"), [output, minOut]);
const v4Input = encodeAbiParameters(parseAbiParameters("bytes, bytes[]"), ["0x060c0f", [params, settle, take]]);
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
return send({
address: ROUTER, abi: routerAbi, functionName: "execute",
args: ["0x10", [v4Input], deadline], value,
} as never);
};
// A HYPE-quoted pool always has HYPE as currency0, so a buy is zeroForOne.
const buyIn = parseEther("0.05");
const buyOut = await quote(true, buyIn); // net of the hook's fee
await swap(true, buyIn, (buyOut * 99n) / 100n, buyIn); // 1% slippage; HYPE goes as value5 · Sell#
Selling spends an ERC-20, so it needs two grants: the token approves Permit2, then Permit2 approves the router. The second one is what most integrations forget.
const erc20Abi = parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]);
const permit2Abi = parseAbi(["function approve(address token, address spender, uint160 amount, uint48 expiration)"]);
const sellIn = tokensOut / 2n;
const expiry = Math.floor(Date.now() / 1000) + 600;
await send({ address: token, abi: erc20Abi, functionName: "approve", args: [PERMIT2, sellIn] } as never);
await send({ address: PERMIT2, abi: permit2Abi, functionName: "approve", args: [token, ROUTER, sellIn, expiry] } as never);
const sellOut = await quote(false, sellIn);
await swap(false, sellIn, (sellOut * 99n) / 100n, 0n); // HYPE comes back to you as native6 · Collect your fees#
Trades leave fees in the hook until someone sweeps them. The keeper does that for every pool on a schedule, and a creator can pay out the quote side at any time. Then the balance is yours to claim from the escrow.
const { poolId } = await client.readContract({
address: FACTORY, abi: factoryAbi, functionName: "getLaunchedToken", args: [token],
}) as { poolId: `0x${string}` };
await send({ address: HOOK, abi: hookAbi, functionName: "sweepPoolQuoteFees", args: [poolId] } as never);
const owed = await client.readContract({
address: ESCROW, abi: escrowAbi, functionName: "balanceOf", args: [account.address],
}) as bigint;
if (owed > 0n) {
await send({ address: ESCROW, abi: escrowAbi, functionName: "claim", args: [] } as never);
}Next#
- Launching a token covers every
TokenParamsfield and common reverts. - Holder rewards sends the creator share to holders instead.
- API gives you quotes, token data and live trades without running an indexer.