{
  "wallet": "5Yqg3rj9MAySqj3RueNZcZSzA51YxC6dX1u2qqJ5vcxV",
  "wallet_chain": "solana",
  "hyperliquid_deployer": {
    "address": null,
    "rule": "evm address, derived from the deployer key at run time and written here by stage_00, never typed by hand",
    "key_source": "env HL_DEPLOYER_PRIVATE_KEY, server side only"
  },
  "network": {
    "name": "hyperliquid",
    "layer": "HyperCore spot, HIP-1 native token",
    "api": "https://api.hyperliquid.xyz",
    "info_endpoint": "https://api.hyperliquid.xyz/info",
    "exchange_endpoint": "https://api.hyperliquid.xyz/exchange",
    "chain_id_for_signing": 1337,
    "signature_chain_id": "0xa4b1",
    "quote_token_index": 0,
    "quote_token_name": "USDC"
  },
  "token": {
    "name": null,
    "full_name": null,
    "sz_decimals": null,
    "wei_decimals": null,
    "max_supply_wei": null,
    "rule": "every null above is filled by the operator before stage_02 runs, the pipeline refuses to start with any null",
    "constraints": {
      "name_max_bytes": 6,
      "sz_decimals_range": [0, 5],
      "wei_decimals_range": [0, 8],
      "wei_minus_sz_max": 8
    }
  },
  "deploy_pipeline": {
    "order": [
      "stage_00_derive_and_check",
      "stage_01_ticker_auction_price",
      "stage_02_register_token",
      "stage_03_user_genesis",
      "stage_04_genesis",
      "stage_05_register_spot_pair",
      "stage_06_register_hyperliquidity",
      "stage_07_set_deployer_fee_share",
      "stage_08_verify"
    ],
    "atomicity": "each stage writes its result to deploy_state.json and the next stage reads from it, a stage that finds its own result already written is skipped, nothing is retried blind",
    "no_time_logic": "no stage waits on a timer, each stage is triggered by the confirmed response of the previous one"
  },
  "signing": {
    "scheme": "L1 action signing",
    "steps": [
      "msgpack encode the action object with keys in insertion order",
      "append nonce as 8 byte big endian",
      "append 0x00 if no vault address else 0x01 plus the 20 byte vault address",
      "keccak256 the bytes, this is the connection id",
      "build EIP-712 typed data: domain name Exchange, version 1, chainId 1337, verifyingContract 0x0000000000000000000000000000000000000000",
      "primary type Agent with fields source string and connectionId bytes32",
      "source is a for mainnet, b for testnet",
      "sign the typed data with the deployer key, split into r s v",
      "post to /exchange as { action, nonce, signature: { r, s, v }, vaultAddress: null }"
    ],
    "nonce_rule": "unix milliseconds, strictly increasing per deployer address, the pipeline keeps last_nonce in deploy_state.json and uses max(now_ms, last_nonce + 1)"
  },
  "code": {
    "language": "python",
    "dependencies": ["hyperliquid-python-sdk>=0.9.0", "eth-account>=0.10", "msgpack", "requests"],
    "files": {
      "hl_sign.py": "import msgpack\nfrom eth_account import Account\nfrom eth_account.messages import encode_typed_data\nfrom eth_utils import keccak\n\nDOMAIN = {\n    'name': 'Exchange',\n    'version': '1',\n    'chainId': 1337,\n    'verifyingContract': '0x0000000000000000000000000000000000000000',\n}\nTYPES = {\n    'Agent': [\n        {'name': 'source', 'type': 'string'},\n        {'name': 'connectionId', 'type': 'bytes32'},\n    ]\n}\n\ndef address_to_bytes(addr):\n    return bytes.fromhex(addr[2:] if addr.startswith('0x') else addr)\n\ndef action_hash(action, vault_address, nonce):\n    data = msgpack.packb(action)\n    data += nonce.to_bytes(8, 'big')\n    if vault_address is None:\n        data += b'\\x00'\n    else:\n        data += b'\\x01' + address_to_bytes(vault_address)\n    return keccak(data)\n\ndef sign_l1_action(private_key, action, vault_address, nonce, is_mainnet=True):\n    connection_id = action_hash(action, vault_address, nonce)\n    message = {'source': 'a' if is_mainnet else 'b', 'connectionId': connection_id}\n    signable = encode_typed_data(domain_data=DOMAIN, message_types=TYPES, message_data=message)\n    signed = Account.sign_message(signable, private_key)\n    return {'r': hex(signed.r), 's': hex(signed.s), 'v': signed.v}\n",
      "hl_post.py": "import json, time, requests\nfrom hl_sign import sign_l1_action\n\nEXCHANGE = 'https://api.hyperliquid.xyz/exchange'\nINFO = 'https://api.hyperliquid.xyz/info'\n\ndef next_nonce(state):\n    now = int(time.time() * 1000)\n    n = max(now, state.get('last_nonce', 0) + 1)\n    state['last_nonce'] = n\n    return n\n\ndef post_action(private_key, action, state, vault_address=None):\n    nonce = next_nonce(state)\n    sig = sign_l1_action(private_key, action, vault_address, nonce, is_mainnet=True)\n    payload = {'action': action, 'nonce': nonce, 'signature': sig, 'vaultAddress': vault_address}\n    r = requests.post(EXCHANGE, json=payload, headers={'Content-Type': 'application/json'}, timeout=30)\n    r.raise_for_status()\n    body = r.json()\n    if body.get('status') != 'ok':\n        raise RuntimeError(json.dumps(body))\n    return body\n\ndef info(req):\n    r = requests.post(INFO, json=req, timeout=30)\n    r.raise_for_status()\n    return r.json()\n",
      "state.py": "import json, os\n\nPATH = 'deploy_state.json'\n\ndef load():\n    if not os.path.exists(PATH):\n        return {'stages': {}, 'last_nonce': 0}\n    with open(PATH) as f:\n        return json.load(f)\n\ndef save(state):\n    tmp = PATH + '.tmp'\n    with open(tmp, 'w') as f:\n        json.dump(state, f, indent=2)\n    os.replace(tmp, PATH)\n\ndef done(state, stage):\n    return stage in state['stages']\n\ndef record(state, stage, result):\n    state['stages'][stage] = result\n    save(state)\n",
      "spec.py": "import json\n\nSPEC = json.load(open('first-official-launch.json'))\nTOKEN = SPEC['token']\nNET = SPEC['network']\n\nREQUIRED = ['name', 'full_name', 'sz_decimals', 'wei_decimals', 'max_supply_wei']\n\ndef check_spec():\n    missing = [k for k in REQUIRED if TOKEN.get(k) is None]\n    if missing:\n        raise SystemExit('spec incomplete, fill: ' + ', '.join(missing))\n    c = TOKEN['constraints']\n    assert len(TOKEN['name'].encode()) <= c['name_max_bytes']\n    assert c['sz_decimals_range'][0] <= TOKEN['sz_decimals'] <= c['sz_decimals_range'][1]\n    assert c['wei_decimals_range'][0] <= TOKEN['wei_decimals'] <= c['wei_decimals_range'][1]\n    assert TOKEN['wei_decimals'] - TOKEN['sz_decimals'] <= c['wei_minus_sz_max']\n    assert TOKEN['wei_decimals'] >= TOKEN['sz_decimals']\n",
      "stage_00_derive_and_check.py": "import os, json\nfrom eth_account import Account\nimport state as S\nfrom spec import check_spec\nfrom hl_post import info\n\ndef run():\n    st = S.load()\n    if S.done(st, 'stage_00'):\n        return st['stages']['stage_00']\n    check_spec()\n    pk = os.environ['HL_DEPLOYER_PRIVATE_KEY']\n    acct = Account.from_key(pk)\n    addr = acct.address\n    st_user = info({'type': 'clearinghouseState', 'user': addr})\n    spot_state = info({'type': 'spotClearinghouseState', 'user': addr})\n    usdc = next((b for b in spot_state.get('balances', []) if b['coin'] == 'USDC'), None)\n    result = {\n        'deployer_address': addr,\n        'spot_usdc_total': usdc['total'] if usdc else '0',\n        'perp_account_value': st_user['marginSummary']['accountValue'],\n    }\n    spec = json.load(open('first-official-launch.json'))\n    spec['hyperliquid_deployer']['address'] = addr\n    json.dump(spec, open('first-official-launch.json', 'w'), indent=2)\n    S.record(st, 'stage_00', result)\n    return result\n\nif __name__ == '__main__':\n    print(json.dumps(run(), indent=2))\n",
      "stage_01_ticker_auction_price.py": "import json\nimport state as S\nfrom hl_post import info\n\ndef run():\n    st = S.load()\n    if S.done(st, 'stage_01'):\n        return st['stages']['stage_01']\n    auction = info({'type': 'spotDeployState', 'user': st['stages']['stage_00']['deployer_address']})\n    gas = auction.get('gasAuction', {})\n    result = {\n        'auction_raw': gas,\n        'current_gas_usdc': gas.get('currentGas'),\n        'start_gas_usdc': gas.get('startGas'),\n        'end_gas_usdc': gas.get('endGas'),\n        'auction_start_time_s': gas.get('startTimeSeconds'),\n        'auction_duration_s': gas.get('durationSeconds'),\n    }\n    S.record(st, 'stage_01', result)\n    return result\n\nif __name__ == '__main__':\n    print(json.dumps(run(), indent=2))\n",
      "stage_02_register_token.py": "import os, json\nimport state as S\nfrom spec import TOKEN\nfrom hl_post import post_action, info\n\ndef run():\n    st = S.load()\n    if S.done(st, 'stage_02'):\n        return st['stages']['stage_02']\n    gas = st['stages']['stage_01']['current_gas_usdc']\n    if gas is None:\n        raise SystemExit('no auction price read, rerun stage_01')\n    max_gas = int(float(gas) * 1e6)\n    action = {\n        'type': 'spotDeploy',\n        'registerToken2': {\n            'spec': {\n                'name': TOKEN['name'],\n                'szDecimals': TOKEN['sz_decimals'],\n                'weiDecimals': TOKEN['wei_decimals'],\n            },\n            'maxGas': max_gas,\n            'fullName': TOKEN['full_name'],\n        },\n    }\n    body = post_action(os.environ['HL_DEPLOYER_PRIVATE_KEY'], action, st)\n    S.save(st)\n    token_index = body['response']['data']\n    meta = info({'type': 'spotMeta'})\n    tok = next(t for t in meta['tokens'] if t['index'] == token_index)\n    result = {\n        'token_index': token_index,\n        'token_id': tok['tokenId'],\n        'name': tok['name'],\n        'sz_decimals': tok['szDecimals'],\n        'wei_decimals': tok['weiDecimals'],\n        'max_gas_paid_wei_usdc': max_gas,\n        'raw': body,\n    }\n    S.record(st, 'stage_02', result)\n    return result\n\nif __name__ == '__main__':\n    print(json.dumps(run(), indent=2))\n",
      "stage_03_user_genesis.py": "import os, json\nimport state as S\nfrom spec import TOKEN\nfrom hl_post import post_action\n\n# genesis allocation: every wei of max supply goes to the deployer address, nothing to anyone else\n# the deployer then seeds hyperliquidity in stage_06 from this balance\n\ndef run():\n    st = S.load()\n    if S.done(st, 'stage_03'):\n        return st['stages']['stage_03']\n    token = st['stages']['stage_02']['token_index']\n    deployer = st['stages']['stage_00']['deployer_address']\n    action = {\n        'type': 'spotDeploy',\n        'userGenesis': {\n            'token': token,\n            'userAndWei': [[deployer.lower(), str(TOKEN['max_supply_wei'])]],\n            'existingTokenAndWei': [],\n        },\n    }\n    body = post_action(os.environ['HL_DEPLOYER_PRIVATE_KEY'], action, st)\n    S.save(st)\n    result = {'token_index': token, 'allocations': action['userGenesis']['userAndWei'], 'raw': body}\n    S.record(st, 'stage_03', result)\n    return result\n\nif __name__ == '__main__':\n    print(json.dumps(run(), indent=2))\n",
      "stage_04_genesis.py": "import os, json\nimport state as S\nfrom spec import TOKEN\nfrom hl_post import post_action, info\n\ndef run():\n    st = S.load()\n    if S.done(st, 'stage_04'):\n        return st['stages']['stage_04']\n    token = st['stages']['stage_02']['token_index']\n    action = {\n        'type': 'spotDeploy',\n        'genesis': {\n            'token': token,\n            'maxSupply': str(TOKEN['max_supply_wei']),\n            'noHyperliquidity': False,\n        },\n    }\n    body = post_action(os.environ['HL_DEPLOYER_PRIVATE_KEY'], action, st)\n    S.save(st)\n    deployer = st['stages']['stage_00']['deployer_address']\n    bal = info({'type': 'spotClearinghouseState', 'user': deployer})\n    mine = next((b for b in bal['balances'] if b['token'] == token), None)\n    result = {\n        'token_index': token,\n        'max_supply_wei': str(TOKEN['max_supply_wei']),\n        'deployer_balance_after_genesis': mine['total'] if mine else None,\n        'raw': body,\n    }\n    S.record(st, 'stage_04', result)\n    return result\n\nif __name__ == '__main__':\n    print(json.dumps(run(), indent=2))\n",
      "stage_05_register_spot_pair.py": "import os, json\nimport state as S\nfrom spec import NET\nfrom hl_post import post_action, info\n\ndef run():\n    st = S.load()\n    if S.done(st, 'stage_05'):\n        return st['stages']['stage_05']\n    token = st['stages']['stage_02']['token_index']\n    action = {\n        'type': 'spotDeploy',\n        'registerSpot': {\n            'tokens': [token, NET['quote_token_index']],\n        },\n    }\n    body = post_action(os.environ['HL_DEPLOYER_PRIVATE_KEY'], action, st)\n    S.save(st)\n    spot_index = body['response']['data']\n    meta = info({'type': 'spotMeta'})\n    pair = next(u for u in meta['universe'] if u['index'] == spot_index)\n    result = {\n        'spot_index': spot_index,\n        'pair_name': pair['name'],\n        'tokens': pair['tokens'],\n        'raw': body,\n    }\n    S.record(st, 'stage_05', result)\n    return result\n\nif __name__ == '__main__':\n    print(json.dumps(run(), indent=2))\n",
      "stage_06_register_hyperliquidity.py": "import os, json\nimport state as S\nfrom spec import TOKEN\nfrom hl_post import post_action\n\n# hyperliquidity parameters are filled by the operator in hl_params.json before this stage\n# start_px: first ask price in USDC, order_sz: size per level in token units, n_orders: number of ask levels\n# n_seeded_levels: how many of those levels are seeded with USDC on the bid side\n\ndef run():\n    st = S.load()\n    if S.done(st, 'stage_06'):\n        return st['stages']['stage_06']\n    p = json.load(open('hl_params.json'))\n    for k in ['start_px', 'order_sz', 'n_orders', 'n_seeded_levels']:\n        if p.get(k) is None:\n            raise SystemExit('hl_params.json missing ' + k)\n    spot = st['stages']['stage_05']['spot_index']\n    action = {\n        'type': 'spotDeploy',\n        'registerHyperliquidity': {\n            'spot': spot,\n            'startPx': str(p['start_px']),\n            'orderSz': str(p['order_sz']),\n            'nOrders': int(p['n_orders']),\n            'nSeededLevels': int(p['n_seeded_levels']),\n        },\n    }\n    body = post_action(os.environ['HL_DEPLOYER_PRIVATE_KEY'], action, st)\n    S.save(st)\n    levels = []\n    px = float(p['start_px'])\n    for i in range(int(p['n_orders'])):\n        levels.append({'level': i, 'px': round(px * (1.003 ** i), 10), 'sz': str(p['order_sz'])})\n    result = {'spot_index': spot, 'params': p, 'computed_ask_ladder_0_3pct_step': levels, 'raw': body}\n    S.record(st, 'stage_06', result)\n    return result\n\nif __name__ == '__main__':\n    print(json.dumps(run(), indent=2))\n",
      "stage_07_set_deployer_fee_share.py": "import os, json\nimport state as S\nfrom hl_post import post_action\n\n# share is a percentage string, max 100%, the deployer's cut of trading fees on this token's pairs\n\ndef run():\n    st = S.load()\n    if S.done(st, 'stage_07'):\n        return st['stages']['stage_07']\n    p = json.load(open('hl_params.json'))\n    if p.get('deployer_fee_share') is None:\n        raise SystemExit('hl_params.json missing deployer_fee_share')\n    token = st['stages']['stage_02']['token_index']\n    action = {\n        'type': 'spotDeploy',\n        'setDeployerTradingFeeShare': {\n            'token': token,\n            'share': str(p['deployer_fee_share']),\n        },\n    }\n    body = post_action(os.environ['HL_DEPLOYER_PRIVATE_KEY'], action, st)\n    S.save(st)\n    result = {'token_index': token, 'share': str(p['deployer_fee_share']), 'raw': body}\n    S.record(st, 'stage_07', result)\n    return result\n\nif __name__ == '__main__':\n    print(json.dumps(run(), indent=2))\n",
      "stage_08_verify.py": "import json\nimport state as S\nfrom hl_post import info\n\ndef run():\n    st = S.load()\n    s = st['stages']\n    token = s['stage_02']['token_index']\n    spot = s['stage_05']['spot_index']\n    deployer = s['stage_00']['deployer_address']\n    meta = info({'type': 'spotMetaAndAssetCtxs'})\n    universe = meta[0]['universe']\n    ctxs = meta[1]\n    pair_pos = next(i for i, u in enumerate(universe) if u['index'] == spot)\n    ctx = ctxs[pair_pos]\n    tok_details = info({'type': 'tokenDetails', 'tokenId': s['stage_02']['token_id']})\n    book = info({'type': 'l2Book', 'coin': universe[pair_pos]['name']})\n    checks = {\n        'token_registered': tok_details.get('name') == s['stage_02']['name'],\n        'genesis_done': tok_details.get('totalSupply') is not None,\n        'deployer_matches': tok_details.get('deployer', '').lower() == deployer.lower(),\n        'pair_live': ctx is not None,\n        'book_has_asks': len(book['levels'][1]) > 0,\n        'book_has_bids': len(book['levels'][0]) > 0,\n        'mark_px': ctx.get('markPx'),\n        'mid_px': ctx.get('midPx'),\n        'circulating_supply': ctx.get('circulatingSupply'),\n        'total_supply': ctx.get('totalSupply'),\n    }\n    S.record(st, 'stage_08', checks)\n    return checks\n\nif __name__ == '__main__':\n    print(json.dumps(run(), indent=2))\n",
      "run_all.py": "import importlib, json, sys\nSTAGES = [\n    'stage_00_derive_and_check',\n    'stage_01_ticker_auction_price',\n    'stage_02_register_token',\n    'stage_03_user_genesis',\n    'stage_04_genesis',\n    'stage_05_register_spot_pair',\n    'stage_06_register_hyperliquidity',\n    'stage_07_set_deployer_fee_share',\n    'stage_08_verify',\n]\nif __name__ == '__main__':\n    stop_after = sys.argv[1] if len(sys.argv) > 1 else STAGES[-1]\n    for name in STAGES:\n        mod = importlib.import_module(name)\n        out = mod.run()\n        print('==', name)\n        print(json.dumps(out, indent=2))\n        if name == stop_after:\n            break\n",
      "hl_params.json": "{\n  \"start_px\": null,\n  \"order_sz\": null,\n  \"n_orders\": null,\n  \"n_seeded_levels\": null,\n  \"deployer_fee_share\": null\n}\n"
    }
  },
  "actions_reference": {
    "registerToken2": {
      "fields": {"spec.name": "string, ticker, max 6 bytes", "spec.szDecimals": "u8", "spec.weiDecimals": "u8", "maxGas": "u64, max USDC in 1e6 units you are willing to pay in the ticker gas auction", "fullName": "string"},
      "returns": "token index u32"
    },
    "userGenesis": {
      "fields": {"token": "u32", "userAndWei": "list of [address, wei string]", "existingTokenAndWei": "list of [token index, wei string] for proportional airdrops to holders of an existing token"},
      "returns": "ok"
    },
    "genesis": {
      "fields": {"token": "u32", "maxSupply": "wei string, must equal the sum of all userGenesis allocations", "noHyperliquidity": "bool, true skips the hyperliquidity deployer balance requirement"},
      "returns": "ok, after this the supply is fixed forever"
    },
    "registerSpot": {
      "fields": {"tokens": "[base token index, quote token index]"},
      "returns": "spot pair index u32"
    },
    "registerHyperliquidity": {
      "fields": {"spot": "u32", "startPx": "string", "orderSz": "string", "nOrders": "u64", "nSeededLevels": "u64"},
      "returns": "ok, the deployer balance is moved into the onchain hyperliquidity strategy"
    },
    "setDeployerTradingFeeShare": {
      "fields": {"token": "u32", "share": "string percentage like 100%"},
      "returns": "ok"
    }
  },
  "info_queries_used": [
    {"type": "spotMeta"},
    {"type": "spotMetaAndAssetCtxs"},
    {"type": "spotDeployState", "user": "deployer address"},
    {"type": "spotClearinghouseState", "user": "deployer address"},
    {"type": "clearinghouseState", "user": "deployer address"},
    {"type": "tokenDetails", "tokenId": "0x hex token id from spotMeta"},
    {"type": "l2Book", "coin": "pair name from spotMeta universe"}
  ],
  "state_file_shape": {
    "last_nonce": "u64 ms",
    "stages": {
      "stage_00": {"deployer_address": "0x", "spot_usdc_total": "string", "perp_account_value": "string"},
      "stage_01": {"current_gas_usdc": "string or null", "auction_raw": "object"},
      "stage_02": {"token_index": "u32", "token_id": "0x", "name": "string"},
      "stage_03": {"allocations": "list"},
      "stage_04": {"deployer_balance_after_genesis": "string"},
      "stage_05": {"spot_index": "u32", "pair_name": "string"},
      "stage_06": {"params": "object", "computed_ask_ladder_0_3pct_step": "list"},
      "stage_07": {"share": "string"},
      "stage_08": {"token_registered": "bool", "pair_live": "bool", "book_has_asks": "bool", "book_has_bids": "bool"}
    }
  },
  "failure_handling": {
    "signature_rejected": "check chainId 1337, source a, msgpack key order matches the action dict order exactly, nonce not reused",
    "auction_price_null": "spotDeployState returned no gasAuction, deployer has no open auction window, read again after the next confirmed block",
    "genesis_sum_mismatch": "maxSupply must equal the total of userAndWei, fix TOKEN.max_supply_wei and delete stage_03 from deploy_state.json before rerunning",
    "insufficient_usdc": "the deployer spot USDC balance must cover maxGas plus hyperliquidity seeding, fund the deployer and rerun stage_00"
  }
}
