Elysium
The issuer ceremony for taking a token onto HyperCore: buying the ticker at the spot-deploy auction, creating the deposit wallet, genesis, linking, and listing on a spot order book.
Last updated
An issuer guide for taking a HyperEVM token onto HyperCore: buy the ticker at the spot-deploy auction, create its deposit wallet, genesis the Core token, link it to the wallet, and list it on a Core spot order book.
This is the issuer-side ceremony that precedes HyperCore bridging via deposit wallets — that guide covers the runtime deposit/withdraw flow once the link below is finalized. See Token bridging on Elysium for how this leg composes with the Elysium ↔ HyperEVM legs: an Elysium-native token first arrives on HyperEVM as a bridge mirror, then graduates through this exact ceremony run on the mirror.
The Hyperliquid protocol side of this ceremony (the auction, genesis, linking, listing) runs today, testnet and mainnet alike — only the API endpoints change. The deposit-wallet contracts are pre-launch: the factory address is published at launch, so the guide walks a hypothetical deployment (ticker ABC, Core token index 1234) with named constants for every address.
1. Overview
Spot deployment is a sequence of deployer actions submitted to the Core exchange endpoint. The lifecycle:
ticker won → deposit wallet created → genesis staged → genesis done
→ link requested → linked (deposits open) → listed on a spot book- Steps 0–3 (auction → genesis → link) are what bridging requires — after the link finalizes, deposits and withdrawals work.
- Step 4 (listing) is optional and can wait: until it runs, explorers and deploy UIs show the token as a pending token, but the wallet, link, and balances are unaffected.
- In practice the ceremony spans days (auction timing, announcement timing). There is no deadline between winning the ticker and the later steps.
Only the deployer — the address that won the ticker — can perform any of these actions for that token. Link requests are per-ticker and deployer-gated, so no third party can link or list against your ticker. The inverse also holds: the whole path is permissionless — every action is signed by the deployer alone, and no allowlist, review, or third-party approval exists anywhere between buying the ticker and a live order book.
The full flow at a glance
The complete run, in order, with each step's blocker inline — the rest of this guide expands each item:
- Buy the ticker at the auction (step 0). Blocker: the decimal spec is locked forever at purchase — plan it first (step 1).
- EVM side: create the deposit wallet on the factory (the deposit wallet) — the contract HyperCore will link to, so your token needs no bridge code. Blockers: the wallet must be created while the ticker is still unlinked, and its
hyperCoreDeployermust be the key that will sign the link's finalize. - Genesis — stage with
userGenesis, finalize withgenesis(step 2). Blockers: one-shot and irreversible;maxSupplymust equal the staged sum; for a bridged token the system address must end up holding the full supply. - Link —
requestEvmContract, thenfinalizeEvmContract(step 3). Blockers: genesis strictly first; the finalize signature must come from the key in the wallet's finalizer slot; the link is permanent. - Bridging is now live — deposits and withdrawals per the bridging guide. The token shows as pending on explorers until listed; that is cosmetic.
- List — enable your quote asset if it isn't USDC, then
registerSpot, thenregisterHyperliquidity(step 4). Blockers: a non-USDC quote needsenableQuoteTokenfirst;registerSpotalone does not list;nOrdersmust be0for anoHyperliquiditytoken. - Verify the pair in
spotMeta.universe— done.
One operational habit worth copying: dry-run first. Build and print the exact action dict before signing anything — genesis and finalizeEvmContract are one-shot, and the printed JSON is exactly what the signature will cover.
Two different signing families. Every action in this guide is an exchange L1 action, signed by the Python SDK'ssign_l1_action— a different signing scheme from the user-signed family (HyperliquidTransaction:*types) used bysendAsset. Use the SDK for both; it is the reference encoder, and hand-rolling the wrong family produces signature errors.
All Python below assumes the official Python SDK:
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
from eth_account import Account
# TESTNET_API_URL = "https://api.hyperliquid-testnet.xyz"
# MAINNET_API_URL = "https://api.hyperliquid.xyz"
deployer = Account.from_key(DEPLOYER_KEY) # the auction-winning key
exchange = Exchange(deployer, base_url=API_URL)
info = Info(base_url=API_URL, skip_ws=True)Example ceremony at a glance (hypothetical)
The same hypothetical deployment as the bridging guide's example; for a real token, every row is readable live from spotMeta / tokenDetails:
Value | |
|---|---|
HyperCore ticker |
|
Core token index |
|
Core token id |
|
System address ( |
|
Underlying ERC-20 (HyperEVM) |
|
Deposit wallet (= linked |
|
Max supply |
|
|
|
Worth internalizing early: a token that has completed steps 0–3 is linked and bridging (deposits/withdrawals work) while still showing as a pending token on explorers — the listing step is independent and can wait.
2. Step 0 — buy the ticker (spot-deploy auction)
Tickers are sold in a recurring Dutch auction (≈31-hour cycles, price descending to a floor). Winning registerToken2 locks the ticker name and the token's decimal spec; supply does not exist yet, and nothing else about the token is committed. The live auction — current price and recent ticker sales — is visible on the hypurrscan dashboard (the token-auction section); auction cadence, the pricing curve, and current gas are best read live and from the official docs — Deploying HIP-1 and HIP-2 assets and HIP-1.
Spec constraints to decide before bidding (they are permanent):
name— at most 6 characters.szDecimals/weiDecimals— must satisfyszDecimals + 5 <= weiDecimals. For a token bridging from an 8-decimal EVM underlying,weiDecimals 8is the natural choice (see amounts & decimals).
# Live auction state for your deployer address (also shows your ceremony progress later)
state = info.post("/info", {"type": "spotDeployState", "user": deployer.address})
exchange.spot_deploy_register_token(
TICKER_NAME, # <= 6 chars, e.g. "ABC"
SZ_DECIMALS, # e.g. 2
WEI_DECIMALS, # e.g. 8
MAX_GAS, # your auction bid ceiling — reverts if the current price exceeds it
FULL_NAME, # display name, e.g. "ABC Token"
)After winning, spotDeployState shows the spec locked with maxSupply: null, zero genesis balances, and no spot pair — that is the expected state until genesis.
3. Step 1 — decimals & supply planning
Core amounts are uint64 in Core wei (weiDecimals). The EVM link scales by evmExtraWeiDecimals = underlyingDecimals − weiDecimals, allowed in [-2, 18]:
Underlying |
|
|
|---|---|---|
8 | 8 |
|
6 (USDC-style) | 8 |
|
18 | 8 |
|
Sub-weiDecimals remainders on transfers into Core are burned, and the whole genesis supply must fit uint64 Core wei with headroom — pick a real max supply, never exactly 2^64 − 1 (the extreme value can trip an internal overflow guard at genesis). For a token bridging via a deposit wallet, max supply should cover the underlying's entire EVM supply, since the system address must be able to back every possible deposit. Worked conversions: Amounts & decimals.
4. The deposit wallet — create it on HyperEVM
This guide assumes the EVM contract being linked is a deposit wallet — the escrow adapter from HyperCore bridging via deposit wallets. The wallet, not your ERC-20, is what HyperCore links to; that indirection is why any existing token graduates with zero token-code changes. If you arrived here from that guide: this is the same factory whose walletFor / predictWallet getters resolve wallets — creation is the one issuer-side write on the same (underlying, tickerIndex, hyperCoreDeployer) tuple, and it is the call to make when walletFor returns 0x0 for your token.
// DEPOSIT_WALLET_FACTORY: published at launch.
interface IHyperCoreDepositFactory {
function createWallet(address underlying, uint64 tickerIndex, address hyperCoreDeployer)
external returns (address wallet);
function predictWallet(address underlying, uint64 tickerIndex, address hyperCoreDeployer)
external view returns (address); // deterministic address, deployed or not
function walletFor(address underlying, uint64 tickerIndex, address hyperCoreDeployer)
external view returns (address); // deployed address, or 0x0 if none
}IHyperCoreDepositFactory factory = IHyperCoreDepositFactory(DEPOSIT_WALLET_FACTORY);
address predicted = factory.predictWallet(UNDERLYING, TICKER_INDEX, HYPERCORE_DEPLOYER);
address wallet = factory.createWallet(UNDERLYING, TICKER_INDEX, HYPERCORE_DEPLOYER);
// wallet == predicted, and walletFor(...) now returns itThe parameters, precisely:
underlying— the ERC-20 the wallet escrows (for an Elysium-native token, its bridge mirror on HyperEVM).tickerIndex— the Core token index from step 0.hyperCoreDeployer— the address that will later signfinalizeEvmContract. The factory writes it into the wallet'skeccak256("HyperCore deployer")slot, which is exactly what thecustomStorageSlotlink proof checks in step 3. Choose it deliberately — for most issuers it is the auction deployer key itself.
Constraints and timing:
- Create the wallet while the ticker is still unlinked — creation validates the ticker against live Core state and requires
evmContract == null. (HYPE's own index is rejected outright: native HYPE bridges through its system address, never a wallet.) - Creation is permissionless and moves no funds; anyone can deploy the wallet for a
(token, ticker, finalizer)tuple, and the caller gains no rights — the finalizer key is what matters. - Ordering is otherwise free: the wallet can be created before or after genesis.
predictWalleteven supports a deploy-first ceremony — request the link against the predicted address and deploy later — but the simplest order is create → genesis → link.
5. Step 2 — genesis
Genesis mints the Core supply. For a bridged token, all of it goes to the token's system address (0x2000…0000 | tokenIndex) so that Core credits are always backed by EVM escrow, and Hyperliquidity is disabled (HIP-2 seeding requires deployer USDC per level and targets fresh launches, not assets with EVM-side price discovery).
Two actions, in order — stage the balances, then finalize:
# 1) Stage: park the full supply at the system address
exchange.spot_deploy_user_genesis(
TICKER_INDEX,
[(SYSTEM_ADDRESS, str(MAX_SUPPLY_WEI))], # amounts are Core-wei strings
[], # no proportional airdrop from an existing token
)
# 2) Finalize: total must equal the staged sum; no_hyperliquidity=True for bridged tokens
exchange.spot_deploy_genesis(TICKER_INDEX, str(MAX_SUPPLY_WEI), True)userGenesis is more general than the bridged-token pattern above, and can be called multiple times to stage allocations before the one-shot genesis seals them:
userAndWeiis a list of[address, coreWei]allocations — any recipients, not only the system address (a non-bridged launch can genesis straight to holders).existingTokenAndWeistages a proportional airdrop to an existing Core token's holders ([tokenIndex, coreWei]per anchor token).- An optional
blacklistUsersfield excludes addresses from an anchor-token airdrop. - Amounts are Core-wei strings; the staged total must fit
uint64, andgenesis.maxSupplymust equal it exactly. Scale human amounts by the token's liveweiDecimals(spotMeta), never by the EVMdecimals().
For a bridged token, keep everything at the system address — any supply genesis'd elsewhere is Core supply not backed by EVM escrow, which breaks the wallet invariant the bridging guide relies on.
genesis is one-shot and irreversible. Preview your inputs carefully. And never re-run it to clear a pending token status later — pending means the listing step hasn't run, not that genesis failed (deploy-UI "trigger genesis" buttons don't observe API-submitted genesis).On the wire, noHyperliquidity is present only when true — the SDK omits the key otherwise, and the signature covers the exact bytes. The action shape (values illustrative; amounts are Core-wei strings):
{"type": "spotDeploy", "genesis": {"token": 1234, "maxSupply": "100000000000000000", "noHyperliquidity": true}}Verify: the system address now holds the max supply.
bal = info.post("/info", {"type": "spotClearinghouseState", "user": SYSTEM_ADDRESS})
# → balances contain the token at exactly MAX_SUPPLY_WEI6. Step 3 — link the ticker to the deposit wallet
Linking binds the Core token to its HyperEVM contract — the deposit wallet created in the previous section, not the underlying ERC-20. Two deployer actions: request, then finalize.
Ordering that matters — genesis strictly before finalize. Core does not check that the system address is funded when the link finalizes, so a linked-but-unfunded token would open deposits against an empty system address. Fund via genesis first, always. (The wallet side has the mirrored rule: the wallet must be created while the ticker is still unlinked.)
The link is permanent. A wrong token index, wrong wallet address, or wrong evmExtraWeiDecimals cannot be corrected — verify each input against the live wallet before finalizing.The SDK has no wrapper for these two actions; sign and submit them as raw L1 actions with the SDK's own signer:
import time, requests
from hyperliquid.utils.signing import sign_l1_action
def submit(action: dict) -> dict:
nonce = int(time.time() * 1000)
sig = sign_l1_action(deployer, action, None, nonce, None, IS_MAINNET)
r = requests.post(f"{API_URL}/exchange", json={
"action": action, "nonce": nonce, "signature": sig,
"vaultAddress": None, "expiresAfter": None,
}, timeout=30)
return r.json()
# 1) Request: nominate the EVM contract. Nested under spotDeploy is the
# canonical form; {"type": "requestEvmContract", ...} also exists as a
# standalone top-level fallback with the same inner fields.
submit({"type": "spotDeploy", "requestEvmContract": {
"token": TICKER_INDEX,
"address": DEPOSIT_WALLET.lower(),
"evmExtraWeiDecimals": EVM_EXTRA_WEI_DECIMALS,
}})
# 2) Finalize: prove control of the EVM contract
submit({"type": "finalizeEvmContract", # note: top-level, not nested under spotDeploy
"token": TICKER_INDEX,
"input": "customStorageSlot"})finalize proves the EVM side by one of three variants:
| Proof | Use when |
|---|---|---|
| the contract was CREATE-deployed by the finalizing key at nonce | plain EOA-deployed contracts |
| storage slot 0 holds the finalizer address | contracts designed for it |
| the slot | deposit wallets (and any CREATE2/factory-deployed contract — there is no CREATE2 proof variant, which is exactly why the named-slot path exists) |
Both actions are Core-side L1 actions — nothing is signed from the EVM side. finalize must be signed by whatever key the wallet's keccak256("HyperCore deployer") slot holds (the wallet records it at creation; it can differ from the auction deployer).
Verify the link:
token = next(t for t in info.spot_meta()["tokens"] if t["index"] == TICKER_INDEX)
# → token["evmContract"]["address"] == the deposit wallet; deposits are now openFrom here on, the runtime flow is HyperCore bridging via deposit wallets.
What a finalized link looks like on-chain: every bridge crossing lands on the token's system address (0x20…04d2 here) — a deposit escrows the underlying in the wallet on HyperEVM and HyperCore credits the recipient's Core spot balance from the system address; a withdrawal is a Core spot send to the system address, which pays out the escrowed underlying on HyperEVM. A Core explorer's view of the system address is therefore the bridge's complete activity feed in both directions.
7. Step 4 — list on a spot book (vs a quote asset)
Optional and independently timed: deposits/withdrawals work without it, but until the sub-steps run, explorers show a pending token. Listing, like everything else here, is permissionless deployer actions only — there is no listing review or approval anywhere.
4a. Choose the quote asset
Books quote against USDC (token index 0) by default, and a USDC quote needs no preparation. To list against a different quote asset, that asset must first be enabled as a quote token — a spot-deploy action run for the quote token itself:
# Enable a token to serve as the quote side of pairs (skip for USDC).
# Listing your own two tokens against each other means running this on
# your quote token; eligibility conditions are defined by the protocol —
# see the official deploy docs.
exchange.spot_deploy_enable_quote_token(QUOTE_TOKEN_INDEX)Note for non-USDC pairs: Hyperliquidity seeding targets USDC-quoted pairs (HIP-2), so list them with nOrders 0 (an empty book) regardless of the genesis flag.
4b. Register the pair, then list it
Two actions, in order — and the second is what actually lists:
# Allocate the spot pair index (QUOTE_TOKEN_INDEX = 0 for USDC).
# The same action also deploys a pair between an existing base and an
# existing (quote-enabled) asset.
exchange.spot_deploy_register_spot(TICKER_INDEX, QUOTE_TOKEN_INDEX)
# The new spot index comes from your deploy state (user-keyed by deployer):
state = info.post("/info", {"type": "spotDeployState", "user": deployer.address})
# → state["states"] carries the ceremony progress; the allocated pair index
# appears under its spots — the pair is NOT tradable yet.
# Finalize the listing. Required even with noHyperliquidity genesis —
# then n_orders MUST be 0 (empty book) and order_sz is formal.
# start_px is the real initial listing price: set it realistically, not 1.
exchange.spot_deploy_register_hyperliquidity(SPOT_INDEX, START_PX, ORDER_SZ, 0, None)Verify: the pair [TICKER_INDEX, QUOTE_TOKEN_INDEX] appears in spotMeta.universe and the pending status clears.
universe = info.spot_meta()["universe"]
assert any(p["tokens"] == [TICKER_INDEX, QUOTE_TOKEN_INDEX] for p in universe)Common failures: registerSpot alone leaves the pair unlisted (the registerHyperliquidity call is the finalizer); a non-USDC quote that was never quote-enabled blocks the pair — run enableQuoteToken on the quote asset first; with nOrders 0, if orderSz 0 is rejected use 1 (it is formal either way); never re-run genesis to clear the pending status.
8. Reference — other spot-deploy actions
The deployer family has a few more members, outside this guide's core path. Payload shapes for orientation; semantics in the official deploy docs:
Action | Payload (inside | Purpose |
|---|---|---|
|
| set the deployer's cut of the pair's trading fees |
|
| opt-in compliance controls; revocation is permanent |
| see step 4b, plus | HIP-2 market-making seed — USDC-quoted pairs, requires deployer USDC (HIP-2) |
9. Amounts & decimals
All amounts in the deployer actions are Core wei as strings (weiDecimals places). The EVM boundary scales by 10^evmExtraWeiDecimals, fixed forever at link time. Alignment rules, worked conversions, and the deposit-side elastic checks live in HyperCore bridging: Amounts & decimals.
On this page