一键发币 v1.0 — Multi-Launchpad Token Launch One-click token creation with optional bundled initial buy across 6+ launchpads. --- Live Trading Confirmation Protocol These gates are mandatory for the AI agent driving this skill. Before any call that signs or broadcasts an on-chain transaction (any , , , or any internal write code path that ends in a real on-chain submission), ALL of the following must be true: 1. Paper / preview mode is the default. Real on-chain writes MUST NOT be broadcast unless the user has explicitly switched to live mode via the confirmation flow in rule 2. If no explicit li…

+(v/1e6).toFixed(2)+'M';\n if(v>=1e3)return '

一键发币 v1.0 — Multi-Launchpad Token Launch One-click token creation with optional bundled initial buy across 6+ launchpads. --- Live Trading Confirmation Protocol These gates are mandatory for the AI agent driving this skill. Before any call that signs or broadcasts an on-chain transaction (any , , , or any internal write code path that ends in a real on-chain submission), ALL of the following must be true: 1. Paper / preview mode is the default. Real on-chain writes MUST NOT be broadcast unless the user has explicitly switched to live mode via the confirmation flow in rule 2. If no explicit li…

+(v/1e3).toFixed(1)+'K';\n if(v>=1)return '

一键发币 v1.0 — Multi-Launchpad Token Launch One-click token creation with optional bundled initial buy across 6+ launchpads. --- Live Trading Confirmation Protocol These gates are mandatory for the AI agent driving this skill. Before any call that signs or broadcasts an on-chain transaction (any , , , or any internal write code path that ends in a real on-chain submission), ALL of the following must be true: 1. Paper / preview mode is the default. Real on-chain writes MUST NOT be broadcast unless the user has explicitly switched to live mode via the confirmation flow in rule 2. If no explicit li…

+v.toFixed(2);\n return '

一键发币 v1.0 — Multi-Launchpad Token Launch One-click token creation with optional bundled initial buy across 6+ launchpads. --- Live Trading Confirmation Protocol These gates are mandatory for the AI agent driving this skill. Before any call that signs or broadcasts an on-chain transaction (any , , , or any internal write code path that ends in a real on-chain submission), ALL of the following must be true: 1. Paper / preview mode is the default. Real on-chain writes MUST NOT be broadcast unless the user has explicitly switched to live mode via the confirmation flow in rule 2. If no explicit li…

+v.toFixed(6);\n}\n\nfunction renderWallets(wr){\n const el=document.getElementById('wallets');\n let h='';\n if(wr.sol&&wr.sol.address)\n h+=`\u003cdiv class=\"wc\">\u003cdiv class=\"wc-i sol\">S\u003c/div>\u003cdiv class=\"wc-d\">\u003cdiv class=\"wc-ch\">Solana\u003c/div>\u003cdiv class=\"wc-a\">${short(wr.sol.address,6)}\u003c/div>\u003cdiv class=\"wc-b\">${wr.sol.balance.toFixed(4)} SOL\u003c/div>\u003c/div>\u003c/div>`;\n if(wr.bsc&&wr.bsc.address)\n h+=`\u003cdiv class=\"wc\">\u003cdiv class=\"wc-i bsc\">B\u003c/div>\u003cdiv class=\"wc-d\">\u003cdiv class=\"wc-ch\">BSC\u003c/div>\u003cdiv class=\"wc-a\">${short(wr.bsc.address,6)}\u003c/div>\u003cdiv class=\"wc-b\">${wr.bsc.balance.toFixed(4)} BNB\u003c/div>\u003c/div>\u003c/div>`;\n el.innerHTML=h;\n const b=document.getElementById('mode-badge');\n if(wr.mode==='LIVE'){b.textContent='● LIVE';b.className='pill pill-live'}\n else{b.textContent='○ DRY RUN';b.className='pill pill-dry'}\n}\n\nfunction renderStats(lr){\n const el=document.getElementById('stats');\n const total=lr.length,ok=lr.filter(l=>l.success).length;\n const pads=new Set(lr.map(l=>l.launchpad)).size;\n const tb=lr.reduce((s,l)=>s+(l.buy_amount||0),0);\n el.innerHTML=`\n \u003cdiv class=\"st\">\u003cdiv class=\"sv\">${total}\u003c/div>\u003cdiv class=\"sl\">Launches\u003c/div>\u003c/div>\n \u003cdiv class=\"st\">\u003cdiv class=\"sv\">${ok}\u003c/div>\u003cdiv class=\"sl\">Success\u003c/div>\u003c/div>\n \u003cdiv class=\"st\">\u003cdiv class=\"sv\">${total-ok}\u003c/div>\u003cdiv class=\"sl\">Failed\u003c/div>\u003c/div>\n \u003cdiv class=\"st\">\u003cdiv class=\"sv\">${pads}\u003c/div>\u003cdiv class=\"sl\">Launchpads\u003c/div>\u003c/div>\n \u003cdiv class=\"st\">\u003cdiv class=\"sv\">${tb>0?tb.toFixed(2):'0'}\u003c/div>\u003cdiv class=\"sl\">Total Buy\u003c/div>\u003c/div>`;\n}\n\nfunction bcHtml(pct){\n if(pct==null)return `\u003cdiv class=\"bc\">\u003cdiv class=\"bc-bg\">\u003cdiv class=\"bc-f lo\" style=\"width:0\">\u003c/div>\u003c/div>\u003cspan class=\"bc-p na\">—\u003c/span>\u003c/div>`;\n const c=pct>=80?'hi':pct>=40?'mi':'lo';\n return `\u003cdiv class=\"bc\">\u003cdiv class=\"bc-bg\">\u003cdiv class=\"bc-f ${c}\" style=\"width:${Math.min(pct,100)}%\">\u003c/div>\u003c/div>\u003cspan class=\"bc-p\">${pct.toFixed(1)}%\u003c/span>\u003c/div>`;\n}\n\nfunction renderRow(l,idx,total,live){\n const lp=l.launchpad||'pumpfun';\n const chain=l.chain||'solana';\n const sym=SYM[chain]||'';\n const isDry=l.token_address&&l.token_address.startsWith('DRY_RUN');\n const isLatest=idx===0;\n const num=total-idx;\n\n const cid=l.image_cid;\n const hasImg=cid&&cid.startsWith('Qm')&&cid.length>10;\n const img=hasImg\n ?`\u003cimg class=\"lc-img\" src=\"${IPFS}${cid}\" alt=\"\" onerror=\"this.outerHTML='\u003cdiv class=lc-ph>${l.symbol?l.symbol[0]:'?'}\u003c/div>'\">`\n :`\u003cdiv class=\"lc-ph\">${l.symbol?l.symbol[0]:'?'}\u003c/div>`;\n\n let badge;\n if(isDry)badge='\u003cspan class=\"bd bd-dr\">DRY\u003c/span>';\n else if(l.success)badge='\u003cspan class=\"bd bd-ok\">LIVE\u003c/span>';\n else badge='\u003cspan class=\"bd bd-no\">FAIL\u003c/span>';\n\n const buy=l.buy_amount>0\n ?`\u003cspan class=\"lc-buy\">+${l.buy_amount} ${sym}\u003c/span>`\n :`\u003cspan class=\"lc-buy z\">—\u003c/span>`;\n\n let soc='';\n if(l.twitter)soc+=`\u003ca href=\"${l.twitter}\" target=\"_blank\">𝕏\u003c/a>`;\n if(l.telegram)soc+=`\u003ca href=\"${l.telegram}\" target=\"_blank\">✈\u003c/a>`;\n if(l.website)soc+=`\u003ca href=\"${l.website}\" target=\"_blank\">⌂\u003c/a>`;\n\n let links='';\n if(l.explorer_url)links+=`\u003ca href=\"${l.explorer_url}\" target=\"_blank\">TX\u003c/a>`;\n if(l.trade_page_url)links+=`\u003ca href=\"${l.trade_page_url}\" target=\"_blank\">Trade\u003c/a>`;\n\n let statsRow='';\n if(live&&live.holders!=null&&!isDry){\n const bp=live.bonding_pct;\n statsRow=`\u003cdiv class=\"lc-sts\">\n \u003cdiv class=\"ls\">\u003cdiv class=\"lv\">${fmtUsd(live.price_usd)}\u003c/div>\u003cdiv class=\"ll\">Price\u003c/div>\u003c/div>\n \u003cdiv class=\"ls\">\u003cdiv class=\"lv\">${fmtUsd(live.mcap_usd)}\u003c/div>\u003cdiv class=\"ll\">MCap\u003c/div>\u003c/div>\n \u003cdiv class=\"ls\">\u003cdiv class=\"lv\">${live.holders||0}\u003c/div>\u003cdiv class=\"ll\">Holders\u003c/div>\u003c/div>\n \u003cdiv class=\"ls\">\u003cdiv class=\"lv\">${fmtUsd(live.volume_1h)}\u003c/div>\u003cdiv class=\"ll\">Vol 1h\u003c/div>\u003c/div>\n \u003cdiv class=\"ls\">\u003cdiv class=\"lv\">${(live.buy_count||0)}/${(live.sell_count||0)}\u003c/div>\u003cdiv class=\"ll\">B/S\u003c/div>\u003c/div>\n \u003cdiv class=\"ls\">${bcHtml(bp)}\u003cdiv class=\"ll\" style=\"margin-top:3px\">Bond\u003c/div>\u003c/div>\n \u003c/div>`;\n }\n\n return `\u003cdiv class=\"lc${isLatest?' is-new':''}\">\n \u003cdiv class=\"lc-top\">\n \u003cspan class=\"lc-n\">${num}\u003c/span>\n ${img}\n \u003cdiv class=\"lc-id\">\n \u003cdiv class=\"lc-nm\">${l.name}\u003cspan class=\"sy\">${l.symbol}\u003c/span>\u003c/div>\n \u003cdiv class=\"lc-ds\">${l.description||''}\u003c/div>\n \u003c/div>\n \u003cdiv class=\"lc-meta\">\n \u003cspan class=\"ch\">${LP[lp]||lp}\u003c/span>\n ${badge}\n ${buy}\n ${soc?`\u003cdiv class=\"lc-soc\">${soc}\u003c/div>`:''}\n \u003cspan class=\"lc-t\">${ago(l.timestamp)}\u003c/span>\n ${links?`\u003cdiv class=\"lc-lnk\">${links}\u003c/div>`:''}\n \u003c/div>\n \u003c/div>${statsRow}\n \u003c/div>`;\n}\n\nasync function fetchLive(launches){\n const real=launches.filter(l=>l.success&&l.token_address&&!l.token_address.startsWith('DRY_RUN'));\n await Promise.all(real.slice(0,6).map(async l=>{\n const k=l.token_address;\n if(statsCache[k]&&(Date.now()-statsCache[k]._ts\u003c30000))return;\n try{\n const c=l.chain==='solana'?'501':'56';\n const r=await fetch(`/api/token-stats?address=${k}&chain=${c}`);\n const d=await r.json();d._ts=Date.now();statsCache[k]=d;\n }catch(e){}\n }));\n}\n\nasync function refresh(){\n try{\n const [lr,wr]=await Promise.all([\n fetch('/api/launches').then(r=>r.json()),\n fetch('/api/wallet').then(r=>r.json())\n ]);\n renderWallets(wr);\n renderStats(lr);\n\n const el=document.getElementById('list');\n const empty=document.getElementById('empty');\n if(!lr.length){empty.style.display='block';el.innerHTML='';return}\n empty.style.display='none';\n\n const sorted=lr.slice().reverse();\n const total=lr.length;\n\n el.innerHTML=sorted.map((l,i)=>renderRow(l,i,total,statsCache[l.token_address]||null)).join('');\n\n fetchLive(lr).then(()=>{\n el.innerHTML=sorted.map((l,i)=>renderRow(l,i,total,statsCache[l.token_address]||null)).join('');\n });\n }catch(e){console.error('refresh',e)}\n}\n\nrefresh();\nsetInterval(refresh,8000);\n\u003c/script>\n\u003c/body>\u003c/html>\n","content_type":"text/html; charset=utf-8","language":"markup","size":17983,"content_sha256":"9c7f4ad6de7fc62fa96139297ffc38132c0fe04fd7f779b78edbe4342dece56a"},{"filename":"ipfs.py","content":"\"\"\"\n一键发币 v1.0 — IPFS Upload\n\nSupports two providers:\n 1. pump.fun /api/ipfs — free, no API key, uploads image + creates metadata in one call\n 2. Pinata — requires PINATA_JWT, separate image + metadata uploads\n\npump.fun provider is preferred (zero setup). Falls back to Pinata if pump.fun fails.\n\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport sys\nimport mimetypes\nfrom pathlib import Path\nfrom typing import Optional\n\nimport httpx\n\n# Ensure skill directory is on sys.path\n_SKILL_DIR = str(Path(__file__).resolve().parent)\nif _SKILL_DIR not in sys.path:\n sys.path.insert(0, _SKILL_DIR)\n\nimport config as C\n\n_PINATA_API = \"https://api.pinata.cloud\"\n_PUMPFUN_IPFS = \"https://pump.fun/api/ipfs\"\n\n\n# ══════════════════════════════════════════════════════════════════════\n# pump.fun IPFS (preferred — free, no API key needed)\n# ══════════════════════════════════════════════════════════════════════\n\ndef upload_via_pumpfun(\n image_path: str,\n name: str,\n symbol: str,\n description: str,\n website: str = \"\",\n twitter: str = \"\",\n telegram: str = \"\",\n) -> dict:\n \"\"\"Upload image + metadata to IPFS via pump.fun's endpoint.\n\n This is a single call that:\n - Uploads the image to IPFS\n - Creates Metaplex-standard metadata JSON\n - Uploads metadata to IPFS\n - Returns both URIs\n\n Args:\n image_path: Local file path or URL to the image\n name, symbol, description: Token info\n website, twitter, telegram: Optional social links\n\n Returns:\n dict with keys: \"image_uri\", \"metadata_uri\", \"image_cid\", \"metadata_cid\"\n \"\"\"\n # Read image\n img_data, filename, content_type = _read_image(image_path)\n\n # Build form data\n form_data = {\n \"name\": name,\n \"symbol\": symbol,\n \"description\": description,\n \"showName\": \"true\",\n }\n if twitter:\n form_data[\"twitter\"] = twitter\n if telegram:\n form_data[\"telegram\"] = telegram\n if website:\n form_data[\"website\"] = website\n\n print(f\" [IPFS] Uploading via pump.fun (free, no key needed)...\")\n\n resp = httpx.post(\n _PUMPFUN_IPFS,\n files={\"file\": (filename, img_data, content_type)},\n data=form_data,\n timeout=C.IPFS_TIMEOUT,\n )\n resp.raise_for_status()\n result = resp.json()\n\n metadata = result.get(\"metadata\", {})\n metadata_uri = result.get(\"metadataUri\", \"\")\n image_uri = metadata.get(\"image\", \"\")\n\n # Extract CIDs from URIs (https://ipfs.io/ipfs/QmXxx → QmXxx)\n image_cid = image_uri.split(\"/ipfs/\")[-1] if \"/ipfs/\" in image_uri else image_uri\n metadata_cid = metadata_uri.split(\"/ipfs/\")[-1] if \"/ipfs/\" in metadata_uri else metadata_uri\n\n print(f\" [IPFS] Image: {image_uri}\")\n print(f\" [IPFS] Metadata: {metadata_uri}\")\n\n return {\n \"image_uri\": image_uri,\n \"metadata_uri\": metadata_uri,\n \"image_cid\": image_cid,\n \"metadata_cid\": metadata_cid,\n }\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Pinata IPFS (fallback — requires PINATA_JWT)\n# ══════════════════════════════════════════════════════════════════════\n\n_jwt: str = \"\"\n\n\ndef _get_jwt() -> str:\n global _jwt\n if not _jwt:\n _jwt = C.PINATA_JWT or os.environ.get(\"PINATA_JWT\", \"\")\n if not _jwt:\n raise RuntimeError(\n \"PINATA_JWT not set. Get a free key at https://app.pinata.cloud/developers/api-keys\\n\"\n \"Then: export PINATA_JWT='your_jwt_token'\"\n )\n return _jwt\n\n\ndef _pinata_headers() -> dict:\n return {\"Authorization\": f\"Bearer {_get_jwt()}\"}\n\n\ndef upload_image_pinata(image_path: str) -> str:\n \"\"\"Upload image to Pinata IPFS. Returns CID.\"\"\"\n img_data, filename, content_type = _read_image(image_path)\n\n resp = httpx.post(\n f\"{_PINATA_API}/pinning/pinFileToIPFS\",\n headers=_pinata_headers(),\n files={\"file\": (filename, img_data, content_type)},\n timeout=C.IPFS_TIMEOUT,\n )\n resp.raise_for_status()\n cid = resp.json()[\"IpfsHash\"]\n print(f\" [IPFS/Pinata] Image uploaded: {cid}\")\n return cid\n\n\ndef upload_metadata_pinata(\n name: str, symbol: str, description: str, image_cid: str,\n website: str = \"\", twitter: str = \"\", telegram: str = \"\",\n) -> str:\n \"\"\"Upload metadata JSON to Pinata IPFS. Returns CID.\"\"\"\n metadata = {\n \"name\": name,\n \"symbol\": symbol,\n \"description\": description,\n \"image\": f\"ipfs://{image_cid}\",\n }\n if website:\n metadata[\"website\"] = website\n if twitter:\n metadata[\"twitter\"] = twitter\n if telegram:\n metadata[\"telegram\"] = telegram\n\n payload = json.dumps(metadata, ensure_ascii=False).encode(\"utf-8\")\n\n resp = httpx.post(\n f\"{_PINATA_API}/pinning/pinFileToIPFS\",\n headers=_pinata_headers(),\n files={\"file\": (f\"{symbol}_metadata.json\", payload, \"application/json\")},\n timeout=C.IPFS_TIMEOUT,\n )\n resp.raise_for_status()\n cid = resp.json()[\"IpfsHash\"]\n print(f\" [IPFS/Pinata] Metadata uploaded: {cid}\")\n return cid\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Smart upload — tries pump.fun first, falls back to Pinata\n# ══════════════════════════════════════════════════════════════════════\n\ndef upload_all(\n image_path: str,\n name: str,\n symbol: str,\n description: str,\n website: str = \"\",\n twitter: str = \"\",\n telegram: str = \"\",\n) -> dict:\n \"\"\"Upload image + metadata to IPFS.\n\n Tries pump.fun endpoint first (free, no API key).\n Falls back to Pinata if pump.fun fails.\n\n Returns:\n dict with: \"image_cid\", \"metadata_cid\", \"metadata_uri\", \"image_uri\"\n \"\"\"\n # Try pump.fun first\n try:\n return upload_via_pumpfun(\n image_path, name, symbol, description,\n website, twitter, telegram,\n )\n except Exception as e:\n print(f\" [IPFS] pump.fun upload failed: {e}\")\n print(f\" [IPFS] Falling back to Pinata...\")\n\n # Fallback: Pinata (requires PINATA_JWT)\n image_cid = upload_image_pinata(image_path)\n metadata_cid = upload_metadata_pinata(\n name, symbol, description, image_cid,\n website, twitter, telegram,\n )\n return {\n \"image_uri\": f\"{C.PINATA_GATEWAY}/{image_cid}\",\n \"metadata_uri\": f\"{C.PINATA_GATEWAY}/{metadata_cid}\",\n \"image_cid\": image_cid,\n \"metadata_cid\": metadata_cid,\n }\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Helpers\n# ══════════════════════════════════════════════════════════════════════\n\ndef _read_image(image_path: str) -> tuple:\n \"\"\"Read image from path, URL, or base64/data-URI.\n\n Returns (data: bytes, filename: str, content_type: str).\n\n Supported inputs:\n - \"/path/to/image.png\" → read from disk\n - \"https://example.com/dog.png\" → download\n - \"data:image/png;base64,iVBOR…\" → decode inline\n - raw base64 string (len > 500) → decode inline\n \"\"\"\n # ── URL ────────────────────────────────────────────────────────────\n if image_path.startswith(\"http://\") or image_path.startswith(\"https://\"):\n resp = httpx.get(image_path, timeout=C.IPFS_TIMEOUT, follow_redirects=True)\n resp.raise_for_status()\n data = resp.content\n content_type = resp.headers.get(\"content-type\", \"image/png\")\n ext = mimetypes.guess_extension(content_type.split(\";\")[0].strip()) or \".png\"\n return data, f\"token_image{ext}\", content_type\n\n # ── Data URI: data:image/png;base64,xxxxx ──────────────────────────\n if image_path.startswith(\"data:\"):\n import base64 as b64\n try:\n header, b64data = image_path.split(\",\", 1)\n content_type = header.split(\":\")[1].split(\";\")[0]\n except (ValueError, IndexError):\n content_type = \"image/png\"\n b64data = image_path.split(\",\")[-1]\n data = b64.b64decode(b64data)\n ext = mimetypes.guess_extension(content_type) or \".png\"\n if len(data) > C.IMAGE_MAX_SIZE:\n raise ValueError(f\"Image too large: {len(data) / 1024 / 1024:.1f} MB (max {C.IMAGE_MAX_SIZE / 1024 / 1024:.0f} MB)\")\n return data, f\"token_image{ext}\", content_type\n\n # ── Raw base64 (long string, not a file path) ─────────────────────\n if len(image_path) > 500 and not os.path.exists(image_path):\n import base64 as b64\n data = b64.b64decode(image_path)\n if len(data) > C.IMAGE_MAX_SIZE:\n raise ValueError(f\"Image too large: {len(data) / 1024 / 1024:.1f} MB (max {C.IMAGE_MAX_SIZE / 1024 / 1024:.0f} MB)\")\n return data, \"token_image.png\", \"image/png\"\n\n # ── File path ──────────────────────────────────────────────────────\n p = Path(image_path)\n if not p.exists():\n raise FileNotFoundError(f\"Image not found: {image_path}\")\n\n size = p.stat().st_size\n if size > C.IMAGE_MAX_SIZE:\n raise ValueError(f\"Image too large: {size / 1024 / 1024:.1f} MB (max {C.IMAGE_MAX_SIZE / 1024 / 1024:.0f} MB)\")\n\n ext = p.suffix.lower().lstrip(\".\")\n if ext not in C.IMAGE_FORMATS:\n raise ValueError(f\"Unsupported image format: .{ext} (supported: {C.IMAGE_FORMATS})\")\n\n data = p.read_bytes()\n content_type = mimetypes.guess_type(str(p))[0] or \"image/png\"\n return data, p.name, content_type\n\n\ndef gateway_url(cid: str) -> str:\n \"\"\"Convert IPFS CID to gateway URL.\"\"\"\n return f\"https://ipfs.io/ipfs/{cid}\"\n\n\ndef ipfs_uri(cid: str) -> str:\n \"\"\"Convert IPFS CID to ipfs:// URI.\"\"\"\n return f\"ipfs://{cid}\"\n","content_type":"text/x-python; charset=utf-8","language":"python","size":11058,"content_sha256":"d919349f2cb979b8b848654c5cfbc8edbd12fb5cc24731b01bb5e3fc96f1fb77"},{"filename":"launchpads/__init__.py","content":"\"\"\"Launchpad adapters for token creation.\"\"\"\nfrom __future__ import annotations\n\nfrom .base import LaunchpadAdapter, LaunchParams, LaunchResult, onchainos_bin\nfrom .pumpfun import PumpFunAdapter\nfrom .bags import BagsAdapter\nfrom .letsbonk import LetsBonkAdapter\nfrom .moonit import MoonitAdapter\nfrom .fourmeme import FourMemeAdapter\nfrom .flap import FlapAdapter\n\nADAPTERS = {\n \"pumpfun\": PumpFunAdapter,\n \"bags\": BagsAdapter,\n \"letsbonk\": LetsBonkAdapter,\n \"moonit\": MoonitAdapter,\n \"fourmeme\": FourMemeAdapter,\n \"flap\": FlapAdapter,\n}\n\n__all__ = [\n \"ADAPTERS\",\n \"LaunchpadAdapter\",\n \"LaunchParams\",\n \"LaunchResult\",\n \"PumpFunAdapter\",\n \"BagsAdapter\",\n \"LetsBonkAdapter\",\n \"MoonitAdapter\",\n \"FourMemeAdapter\",\n \"FlapAdapter\",\n \"get_adapter\",\n]\n\n\ndef get_adapter(launchpad: str) -> LaunchpadAdapter:\n \"\"\"Get the adapter instance for a given launchpad name.\"\"\"\n cls = ADAPTERS.get(launchpad)\n if cls is None:\n supported = \", \".join(ADAPTERS.keys())\n raise ValueError(f\"Unknown launchpad: {launchpad}. Supported: {supported}\")\n return cls()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":1129,"content_sha256":"cae9a6914e1c42255a98757d993cf5a949cb6e0fc5e5d0988ed54cc8a1a9b901"},{"filename":"launchpads/bags.py","content":"\"\"\"\n一键发币 v1.0 — Bags.fm adapter (Official REST API + Meteora DBC).\n\nFlow:\n 1. Upload token info + metadata via Bags API\n 2. Create fee share config (creator % + optional co-earners)\n 3. Create launch transaction with optional initial buy\n 4. Sign via onchainos wallet\n 5. Submit and wait for confirmation\n\nDocs: https://docs.bags.fm/how-to-guides/launch-token\n\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport base64\nimport json\nimport os\n\nimport httpx\n\nimport config as C\nfrom .base import LaunchpadAdapter, LaunchParams, LaunchResult, onchainos_bin\n\n_SOLANA_EXPLORER = \"https://solscan.io/tx\"\n_BAGS_TRADE = \"https://bags.fm/token\"\n\n\nclass BagsAdapter(LaunchpadAdapter):\n\n @property\n def name(self) -> str:\n return \"bags\"\n\n @property\n def display_name(self) -> str:\n return \"Bags.fm\"\n\n @property\n def chain(self) -> str:\n return \"solana\"\n\n def _fee_estimate(self, params: LaunchParams) -> float:\n return 0.015 # Bags fees + rent\n\n async def launch(self, params: LaunchParams) -> LaunchResult:\n \"\"\"Launch a token on Bags.fm.\"\"\"\n\n if C.DRY_RUN:\n return LaunchResult(\n success=True,\n token_address=\"DRY_RUN_BAGS_NO_TOKEN\",\n tx_hash=\"DRY_RUN_BAGS_NO_TX\",\n error=\"DRY_RUN mode — no on-chain TX sent\",\n )\n\n api = C.BAGS_API_BASE\n timeout = httpx.Timeout(30.0)\n\n async with httpx.AsyncClient(timeout=timeout) as client:\n\n # ── 1. Create token info + metadata ───────────────────────\n print(\" [Bags] Creating token info & metadata...\")\n token_info_resp = await client.post(\n f\"{api}/token-launch/create-token-info\",\n json={\n \"name\": params.name,\n \"symbol\": params.symbol,\n \"description\": params.description,\n \"imageUrl\": f\"https://ipfs.io/ipfs/{params.image_cid}\" if params.image_cid else params.metadata_uri,\n \"twitter\": params.twitter or None,\n \"website\": params.website or None,\n \"telegram\": params.telegram or None,\n },\n )\n if token_info_resp.status_code != 200:\n return LaunchResult(\n success=False,\n error=f\"Bags create-token-info failed {token_info_resp.status_code}: {token_info_resp.text}\",\n )\n\n token_info = token_info_resp.json()\n metadata_url = token_info.get(\"metadataUrl\", \"\")\n token_mint = token_info.get(\"tokenMint\", \"\")\n print(f\" [Bags] Token mint: {token_mint}\")\n print(f\" [Bags] Metadata URL: {metadata_url}\")\n\n # ── 2. Create fee share config ────────────────────────────\n print(\" [Bags] Creating fee share config...\")\n fee_claimers = params.extras.get(\"fee_claimers\", C.BAGS_FEE_CLAIMERS)\n\n # If no fee claimers specified, 100% to creator\n if not fee_claimers:\n fee_claimers = [{\"user\": params.wallet_address, \"userBps\": 10000}]\n\n # Validate total bps = 10000\n total_bps = sum(fc.get(\"userBps\", 0) for fc in fee_claimers)\n if total_bps != 10000:\n return LaunchResult(\n success=False,\n error=f\"Fee share BPS must total 10000, got {total_bps}\",\n )\n\n fee_config_resp = await client.post(\n f\"{api}/fee-share/config\",\n json={\n \"payer\": params.wallet_address,\n \"baseMint\": token_mint,\n \"feeClaimers\": fee_claimers,\n },\n )\n if fee_config_resp.status_code != 200:\n return LaunchResult(\n success=False,\n error=f\"Bags fee-share config failed {fee_config_resp.status_code}: {fee_config_resp.text}\",\n )\n\n config_key = fee_config_resp.json().get(\"configKey\", \"\")\n print(f\" [Bags] Fee share config: {config_key}\")\n\n # ── 3. Create launch transaction ──────────────────────────\n print(\" [Bags] Creating launch transaction...\")\n\n # Convert buy amount to lamports (1 SOL = 1_000_000_000 lamports)\n initial_buy_lamports = int(params.buy_amount * 1_000_000_000)\n\n # Get launch wallet from Bags\n wallet_resp = await client.get(\n f\"{api}/token-launch/fee-share/wallet/v2\",\n params={\"walletAddress\": params.wallet_address},\n )\n launch_wallet = params.wallet_address\n if wallet_resp.status_code == 200:\n lw = wallet_resp.json().get(\"launchWallet\")\n if lw:\n launch_wallet = lw\n\n launch_tx_resp = await client.post(\n f\"{api}/token-launch/create-launch-transaction\",\n json={\n \"metadataUrl\": metadata_url,\n \"tokenMint\": token_mint,\n \"launchWallet\": launch_wallet,\n \"initialBuyLamports\": initial_buy_lamports,\n \"configKey\": config_key,\n },\n )\n if launch_tx_resp.status_code != 200:\n return LaunchResult(\n success=False,\n error=f\"Bags create-launch-tx failed {launch_tx_resp.status_code}: {launch_tx_resp.text}\",\n )\n\n tx_data = launch_tx_resp.json()\n serialized_tx = tx_data.get(\"transaction\", \"\")\n\n if not serialized_tx:\n return LaunchResult(\n success=False,\n error=\"Bags returned empty transaction\",\n )\n\n # ── 4. Sign and submit via onchainos ──────────────────────\n print(\" [Bags] Signing and submitting...\")\n tx_hash = await self._sign_and_submit(serialized_tx, params.wallet_address, token_mint)\n\n if not tx_hash:\n return LaunchResult(\n success=False,\n error=\"Failed to sign/submit via onchainos wallet\",\n )\n\n # ── 5. Wait for confirmation ──────────────────────────────\n print(f\" [Bags] TX submitted: {tx_hash}\")\n confirmed = await self._wait_confirmation(tx_hash, params.wallet_address)\n\n return LaunchResult(\n success=confirmed,\n token_address=token_mint,\n tx_hash=tx_hash,\n explorer_url=f\"{_SOLANA_EXPLORER}/{tx_hash}\",\n trade_page_url=f\"{_BAGS_TRADE}/{token_mint}\",\n error=\"\" if confirmed else \"Transaction not confirmed within timeout\",\n )\n\n async def _sign_and_submit(self, serialized_tx: str, wallet_address: str, to_address: str = \"\") -> str:\n \"\"\"Sign and submit unsigned transaction via onchainos TEE wallet.\"\"\"\n try:\n cmd = [\n onchainos_bin(), \"wallet\", \"contract-call\",\n \"--chain\", \"501\",\n \"--to\", to_address or wallet_address,\n \"--unsigned-tx\", serialized_tx,\n \"--biz-type\", \"dex\",\n \"--strategy\", \"one-click-token-launch\",\n ]\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, stderr = await proc.communicate()\n if proc.returncode != 0:\n print(f\" [Bags] contract-call failed: {stderr.decode().strip()}\")\n return \"\"\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n return data.get(\"txHash\", \"\") or output.get(\"txHash\", \"\")\n except Exception as e:\n print(f\" [Bags] Sign/submit error: {e}\")\n return \"\"\n\n async def _wait_confirmation(self, tx_hash: str, wallet_address: str, max_retries: int = 5) -> bool:\n \"\"\"Poll for TX confirmation via onchainos wallet history.\"\"\"\n for i in range(max_retries):\n await asyncio.sleep(5)\n try:\n proc = await asyncio.create_subprocess_exec(\n onchainos_bin(), \"wallet\", \"history\",\n \"--chain\", \"501\",\n \"--tx-hash\", tx_hash,\n \"--address\", wallet_address,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, _ = await proc.communicate()\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n status = data.get(\"status\", \"\") or data.get(\"txStatus\", \"\")\n if status in (\"confirmed\", \"finalized\", \"success\"):\n print(f\" [Bags] Confirmed! ({i + 1} polls)\")\n return True\n except Exception:\n pass\n return False\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9595,"content_sha256":"7b9292ad6a2247782684580fbc3aa7d5ee91739e577552638269e59552888297"},{"filename":"launchpads/base.py","content":"\"\"\"\n一键发币 v1.0 — Launchpad adapter base class.\nAll launchpad adapters inherit from this and implement launch().\n\"\"\"\nfrom __future__ import annotations\n\nimport abc\nimport os\nfrom dataclasses import dataclass, field\nfrom typing import Optional\n\n\ndef onchainos_bin() -> str:\n \"\"\"Resolve the onchainos CLI binary path.\"\"\"\n env = os.environ.get(\"ONCHAINOS_BIN\", \"\")\n if env:\n return env\n home = os.path.expanduser(\"~/.local/bin/onchainos\")\n if os.path.isfile(home):\n return home\n return \"onchainos\" # fallback to PATH\n\n\n@dataclass\nclass LaunchParams:\n \"\"\"Parameters collected from user for token launch.\"\"\"\n\n # ── Required ──────────────────────────────────────────────────────\n name: str # Token name\n symbol: str # Token ticker\n description: str # Token description\n image_path: str # Local file path or URL\n\n # ── Optional socials ──────────────────────────────────────────────\n website: str = \"\"\n twitter: str = \"\"\n telegram: str = \"\"\n\n # ── Launchpad ─────────────────────────────────────────────────────\n launchpad: str = \"pumpfun\" # Launchpad adapter name\n\n # ── Bundled buy ───────────────────────────────────────────────────\n buy_amount: float = 0.0 # Native token amount (0 = create only)\n slippage_bps: int = 1000 # Slippage in basis points (1000 = 10%)\n mev_protection: bool = True # Jito bundle / MEV protection\n\n # ── Wallet (resolved at runtime) ──────────────────────────────────\n wallet_address: str = \"\"\n\n # ── IPFS (resolved during upload) ─────────────────────────────────\n image_cid: str = \"\" # Set after image upload\n metadata_cid: str = \"\" # Set after metadata upload\n metadata_uri: str = \"\" # Full URI for on-chain metadata\n\n # ── Launchpad-specific extras ─────────────────────────────────────\n extras: dict = field(default_factory=dict)\n # Examples:\n # pumpfun: {\"priority_fee\": 0.0005, \"tip_fee\": 0.0001, \"pool\": \"pump\"}\n # bags: {\"fee_claimers\": [...]}\n # flap: {\"buy_tax\": 500, \"sell_tax\": 300, \"migrator\": 1}\n # fourmeme: {\"category\": \"Meme\"}\n\n\n@dataclass\nclass LaunchResult:\n \"\"\"Result returned after a successful token launch.\"\"\"\n\n success: bool\n token_address: str = \"\"\n tx_hash: str = \"\"\n explorer_url: str = \"\"\n trade_page_url: str = \"\"\n error: str = \"\"\n tokens_received: float = 0.0 # If bundled buy, how many tokens received\n raw_response: dict = field(default_factory=dict)\n\n\nclass LaunchpadAdapter(abc.ABC):\n \"\"\"Abstract base class for all launchpad adapters.\"\"\"\n\n @property\n @abc.abstractmethod\n def name(self) -> str:\n \"\"\"Launchpad identifier (e.g. 'pumpfun').\"\"\"\n ...\n\n @property\n @abc.abstractmethod\n def display_name(self) -> str:\n \"\"\"Human-readable name (e.g. 'pump.fun').\"\"\"\n ...\n\n @property\n @abc.abstractmethod\n def chain(self) -> str:\n \"\"\"Chain identifier: 'solana' or 'bsc'.\"\"\"\n ...\n\n @abc.abstractmethod\n async def launch(self, params: LaunchParams) -> LaunchResult:\n \"\"\"Execute the token launch.\n\n The caller is responsible for:\n - Uploading image + metadata to IPFS (sets params.image_cid, metadata_cid, metadata_uri)\n - Resolving wallet address (sets params.wallet_address)\n - Checking balance sufficiency\n\n The adapter is responsible for:\n - Building the launch transaction(s)\n - Signing via onchainos wallet\n - Submitting to chain\n - Returning the result\n \"\"\"\n ...\n\n def estimate_cost(self, params: LaunchParams) -> float:\n \"\"\"Estimate total cost in native token (buy_amount + fees + gas).\"\"\"\n return params.buy_amount + self._fee_estimate(params)\n\n def _fee_estimate(self, params: LaunchParams) -> float:\n \"\"\"Override in subclass for launchpad-specific fee estimates.\"\"\"\n return 0.02 # Default: small buffer for gas/rent\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4750,"content_sha256":"63977d564511c3738d5b902517d9b0cafe8b52a1395c333c54b01ea96aca8d33"},{"filename":"launchpads/flap.py","content":"\"\"\"\n一键发币 v1.0 — Flap.sh adapter (BSC, direct contract interaction).\n\nFlow:\n 1. Upload image + metadata to IPFS (Pinata)\n 2. Call Flap Portal contract newTokenV6() via onchainos wallet contract-call\n 3. Supports tax tokens (buy/sell tax), vanity addresses, PCS V2/V3 migration\n 4. Wait for confirmation\n\nPortal: 0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0 (BNB Mainnet)\nDocs: https://docs.flap.sh/flap/developers/launch-a-token\n\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport json\n\nimport config as C\nfrom .base import LaunchpadAdapter, LaunchParams, LaunchResult, onchainos_bin\n\n_BSC_EXPLORER = \"https://bscscan.com/tx\"\n_FLAP_TRADE = \"https://flap.sh/token\"\n_ZERO_ADDR = \"0x0000000000000000000000000000000000000000\"\n_ZERO_BYTES32 = \"0x\" + \"00\" * 32\n\n\nclass FlapAdapter(LaunchpadAdapter):\n\n @property\n def name(self) -> str:\n return \"flap\"\n\n @property\n def display_name(self) -> str:\n return \"Flap.sh\"\n\n @property\n def chain(self) -> str:\n return \"bsc\"\n\n def _fee_estimate(self, params: LaunchParams) -> float:\n return 0.015 # BNB gas\n\n async def launch(self, params: LaunchParams) -> LaunchResult:\n \"\"\"Launch a token on Flap.sh (BSC) via newTokenV6.\"\"\"\n\n if C.DRY_RUN:\n return LaunchResult(\n success=True,\n token_address=\"DRY_RUN_FLAP_NO_TOKEN\",\n tx_hash=\"DRY_RUN_FLAP_NO_TX\",\n error=\"DRY_RUN mode — no on-chain TX sent\",\n )\n\n portal = C.FLAP_PORTAL\n extras = params.extras\n\n # ── Build newTokenV6 parameters ───────────────────────────────\n buy_tax = extras.get(\"buy_tax\", C.FLAP_BUY_TAX)\n sell_tax = extras.get(\"sell_tax\", C.FLAP_SELL_TAX)\n tax_duration = extras.get(\"tax_duration\", C.FLAP_TAX_DURATION)\n anti_farmer = extras.get(\"anti_farmer\", C.FLAP_ANTI_FARMER)\n migrator_type = extras.get(\"migrator_type\", C.FLAP_MIGRATOR_TYPE)\n dex_id = extras.get(\"dex_id\", C.FLAP_DEX_ID)\n lp_fee_profile = extras.get(\"lp_fee_profile\", C.FLAP_LP_FEE_PROFILE)\n token_version = extras.get(\"token_version\", C.FLAP_TOKEN_VERSION)\n salt = extras.get(\"salt\", _ZERO_BYTES32)\n beneficiary = extras.get(\"beneficiary\", params.wallet_address)\n\n # Tax allocation split\n mkt_bps = extras.get(\"mkt_bps\", C.FLAP_MKT_BPS)\n deflation_bps = extras.get(\"deflation_bps\", C.FLAP_DEFLATION_BPS)\n dividend_bps = extras.get(\"dividend_bps\", C.FLAP_DIVIDEND_BPS)\n lp_bps = extras.get(\"lp_bps\", C.FLAP_LP_BPS)\n\n buy_wei = int(params.buy_amount * 10**18) if params.buy_amount > 0 else 0\n\n # newTokenV6 struct parameter (ABI-encoded as tuple)\n # We pass all fields as a JSON array for onchainos contract-call\n v6_params = {\n \"name\": params.name,\n \"symbol\": params.symbol,\n \"meta\": params.metadata_cid, # IPFS CID (not full URI)\n \"dexThresh\": 0, # Default DEX listing threshold\n \"salt\": salt,\n \"migratorType\": migrator_type,\n \"quoteToken\": _ZERO_ADDR, # address(0) = native BNB\n \"quoteAmt\": buy_wei,\n \"beneficiary\": beneficiary,\n \"permitData\": \"0x\",\n \"extensionID\": _ZERO_BYTES32,\n \"extensionData\": \"0x\",\n \"dexId\": dex_id,\n \"lpFeeProfile\": lp_fee_profile,\n \"buyTaxRate\": buy_tax,\n \"sellTaxRate\": sell_tax,\n \"taxDuration\": tax_duration,\n \"antiFarmerDuration\": anti_farmer,\n \"mktBps\": mkt_bps,\n \"deflationBps\": deflation_bps,\n \"dividendBps\": dividend_bps,\n \"lpBps\": lp_bps,\n \"minimumShareBalance\": 0,\n \"dividendToken\": _ZERO_ADDR,\n \"commissionReceiver\": _ZERO_ADDR,\n \"tokenVersion\": token_version,\n }\n\n print(f\" [Flap] Calling newTokenV6 on portal {portal[:10]}...\")\n if buy_tax > 0 or sell_tax > 0:\n print(f\" [Flap] Tax config: buy={buy_tax}bps sell={sell_tax}bps duration={tax_duration}s\")\n if salt != _ZERO_BYTES32:\n print(f\" [Flap] Vanity salt: {salt[:10]}...\")\n\n # ABI-encode newTokenV6((tuple)) call data\n input_data = self._encode_new_token_v6(\n name=params.name,\n symbol=params.symbol,\n meta=params.metadata_cid,\n dex_thresh=0,\n salt=salt,\n migrator_type=migrator_type,\n quote_token=_ZERO_ADDR,\n quote_amt=buy_wei,\n beneficiary=beneficiary,\n permit_data=b\"\",\n extension_id=_ZERO_BYTES32,\n extension_data=b\"\",\n dex_id=dex_id,\n lp_fee_profile=lp_fee_profile,\n buy_tax=buy_tax,\n sell_tax=sell_tax,\n tax_duration=tax_duration,\n anti_farmer=anti_farmer,\n mkt_bps=mkt_bps,\n deflation_bps=deflation_bps,\n dividend_bps=dividend_bps,\n lp_bps=lp_bps,\n min_share_balance=0,\n dividend_token=_ZERO_ADDR,\n commission_receiver=_ZERO_ADDR,\n token_version=token_version,\n )\n\n cmd = [\n onchainos_bin(), \"wallet\", \"contract-call\",\n \"--chain\", \"56\",\n \"--to\", portal,\n \"--input-data\", input_data,\n \"--biz-type\", \"dex\",\n \"--strategy\", \"one-click-token-launch\",\n ]\n\n if buy_wei > 0:\n cmd.extend([\"--amt\", str(buy_wei)])\n\n try:\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, stderr = await proc.communicate()\n\n if proc.returncode != 0:\n err = stderr.decode().strip() if stderr else \"unknown error\"\n return LaunchResult(\n success=False,\n error=f\"Flap contract-call failed: {err}\",\n )\n\n output = json.loads(stdout.decode())\n tx_hash = output.get(\"data\", {}).get(\"txHash\", \"\") or output.get(\"txHash\", \"\")\n\n except Exception as e:\n return LaunchResult(success=False, error=f\"Flap launch error: {e}\")\n\n if not tx_hash:\n return LaunchResult(success=False, error=\"No tx hash returned from contract-call\")\n\n # ── Wait for BSC confirmation ─────────────────────────────────\n print(f\" [Flap] TX submitted: {tx_hash}\")\n confirmed, token_address = await self._wait_and_parse(tx_hash, params.wallet_address)\n\n return LaunchResult(\n success=confirmed,\n token_address=token_address,\n tx_hash=tx_hash,\n explorer_url=f\"{_BSC_EXPLORER}/{tx_hash}\",\n trade_page_url=f\"{_FLAP_TRADE}/{token_address}\" if token_address else \"\",\n error=\"\" if confirmed else \"Transaction not confirmed within timeout\",\n )\n\n @staticmethod\n def _encode_new_token_v6(**kw) -> str:\n \"\"\"ABI-encode newTokenV6((tuple)) call data.\n\n Selector: keccak256(\"newTokenV6((string,string,string,uint8,bytes32,\n uint8,address,uint256,address,bytes,bytes32,bytes,uint8,uint8,\n uint16,uint16,uint256,uint256,uint16,uint16,uint16,uint16,\n uint256,address,address,uint8))\")[:4] = 0x363eb8e6\n \"\"\"\n selector = \"363eb8e6\"\n\n def _pad32(val: int, signed: bool = False) -> str:\n return val.to_bytes(32, \"big\", signed=signed).hex()\n\n def _pad_addr(addr: str) -> str:\n a = addr.lower().replace(\"0x\", \"\")\n return a.rjust(64, \"0\")\n\n def _pad_bytes32(b32: str) -> str:\n h = b32.replace(\"0x\", \"\")\n return h.ljust(64, \"0\")\n\n def _encode_string(s: str) -> str:\n data = s.encode(\"utf-8\")\n length = len(data)\n padded_len = ((length + 31) // 32) * 32\n return _pad32(length) + data.hex().ljust(padded_len * 2, \"0\")\n\n def _encode_bytes(b: bytes) -> str:\n length = len(b)\n padded_len = ((length + 31) // 32) * 32\n return _pad32(length) + b.hex().ljust(padded_len * 2, \"0\") if length else _pad32(0)\n\n # The struct is encoded as a tuple — outer offset pointer then tuple data\n # For a single tuple param, offset = 32 (0x20)\n outer_offset = _pad32(32)\n\n # Within the tuple: fixed fields inline, dynamic fields (string, bytes) as offsets\n # Field layout (26 fields):\n # 0: name (string, dynamic)\n # 1: symbol (string, dynamic)\n # 2: meta (string, dynamic)\n # 3: dexThresh (uint8)\n # 4: salt (bytes32)\n # 5: migratorType (uint8)\n # 6: quoteToken (address)\n # 7: quoteAmt (uint256)\n # 8: beneficiary (address)\n # 9: permitData (bytes, dynamic)\n # 10: extensionID (bytes32)\n # 11: extensionData (bytes, dynamic)\n # 12-25: uint8/uint16/uint256 (all static)\n\n # 26 slots of 32 bytes each for heads\n head_slots = 26\n head_size = head_slots * 32 # bytes\n\n # Encode dynamic data and compute offsets\n dyn_parts = []\n dyn_offset = head_size\n\n def _add_dynamic(encoded: str) -> str:\n nonlocal dyn_offset\n offset_hex = _pad32(dyn_offset)\n byte_len = len(encoded) // 2\n dyn_offset += byte_len\n dyn_parts.append(encoded)\n return offset_hex\n\n heads = []\n # 0: name\n heads.append(_add_dynamic(_encode_string(kw[\"name\"])))\n # 1: symbol\n heads.append(_add_dynamic(_encode_string(kw[\"symbol\"])))\n # 2: meta\n heads.append(_add_dynamic(_encode_string(kw[\"meta\"])))\n # 3: dexThresh\n heads.append(_pad32(kw[\"dex_thresh\"]))\n # 4: salt\n heads.append(_pad_bytes32(kw[\"salt\"]))\n # 5: migratorType\n heads.append(_pad32(kw[\"migrator_type\"]))\n # 6: quoteToken\n heads.append(_pad_addr(kw[\"quote_token\"]))\n # 7: quoteAmt\n heads.append(_pad32(kw[\"quote_amt\"]))\n # 8: beneficiary\n heads.append(_pad_addr(kw[\"beneficiary\"]))\n # 9: permitData\n heads.append(_add_dynamic(_encode_bytes(kw[\"permit_data\"])))\n # 10: extensionID\n heads.append(_pad_bytes32(kw[\"extension_id\"]))\n # 11: extensionData\n heads.append(_add_dynamic(_encode_bytes(kw[\"extension_data\"])))\n # 12: dexId\n heads.append(_pad32(kw[\"dex_id\"]))\n # 13: lpFeeProfile\n heads.append(_pad32(kw[\"lp_fee_profile\"]))\n # 14: buyTaxRate\n heads.append(_pad32(kw[\"buy_tax\"]))\n # 15: sellTaxRate\n heads.append(_pad32(kw[\"sell_tax\"]))\n # 16: taxDuration\n heads.append(_pad32(kw[\"tax_duration\"]))\n # 17: antiFarmerDuration\n heads.append(_pad32(kw[\"anti_farmer\"]))\n # 18: mktBps\n heads.append(_pad32(kw[\"mkt_bps\"]))\n # 19: deflationBps\n heads.append(_pad32(kw[\"deflation_bps\"]))\n # 20: dividendBps\n heads.append(_pad32(kw[\"dividend_bps\"]))\n # 21: lpBps\n heads.append(_pad32(kw[\"lp_bps\"]))\n # 22: minimumShareBalance\n heads.append(_pad32(kw[\"min_share_balance\"]))\n # 23: dividendToken\n heads.append(_pad_addr(kw[\"dividend_token\"]))\n # 24: commissionReceiver\n heads.append(_pad_addr(kw[\"commission_receiver\"]))\n # 25: tokenVersion\n heads.append(_pad32(kw[\"token_version\"]))\n\n tuple_data = \"\".join(heads) + \"\".join(dyn_parts)\n return \"0x\" + selector + outer_offset + tuple_data\n\n async def _wait_and_parse(self, tx_hash: str, wallet_address: str = \"\", max_retries: int = 6) -> tuple:\n \"\"\"Wait for BSC TX confirmation and extract token address.\n\n Flap emits TokenCreated(ts, creator, nonce, token, name, symbol, meta)\n The `token` parameter is the new token address.\n \"\"\"\n for i in range(max_retries):\n await asyncio.sleep(3)\n try:\n cmd = [\n onchainos_bin(), \"wallet\", \"history\",\n \"--chain\", \"56\",\n \"--tx-hash\", tx_hash,\n ]\n if wallet_address:\n cmd.extend([\"--address\", wallet_address])\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, _ = await proc.communicate()\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n status = data.get(\"status\", \"\") or data.get(\"txStatus\", \"\")\n\n if status in (\"confirmed\", \"finalized\", \"success\", \"1\"):\n token_addr = \"\"\n logs = data.get(\"logs\", [])\n for log in logs:\n topics = log.get(\"topics\", [])\n if len(topics) >= 1 and log.get(\"address\", \"\").lower() == C.FLAP_PORTAL.lower():\n log_data = log.get(\"data\", \"\")\n if len(log_data) >= 130:\n addr_hex = log_data[90:130]\n token_addr = \"0x\" + addr_hex[-40:]\n\n if not token_addr:\n token_addr = data.get(\"contractAddress\", \"\")\n\n print(f\" [Flap] Confirmed! Token: {token_addr or 'parsing...'}\")\n return True, token_addr\n\n except Exception:\n pass\n\n return False, \"\"\n","content_type":"text/x-python; charset=utf-8","language":"python","size":13984,"content_sha256":"24a204bc5aa3aeef65e04ba57f16db2d7d937e7f680bcfe5394d3fc87a14a9bd"},{"filename":"launchpads/fourmeme.py","content":"\"\"\"\n一键发币 v1.0 — Four.Meme adapter (BSC, direct contract interaction).\n\nFlow:\n 1. Upload image + metadata to IPFS (Pinata)\n 2. Call Four.Meme factory contract via onchainos wallet contract-call\n 3. Include msg.value for initial buy (bundled)\n 4. Wait for confirmation\n\nFour.Meme is the largest BSC launchpad. It doesn't expose a public REST API\nfor token creation, so we interact with the factory contract directly.\n\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport json\n\nimport config as C\nfrom .base import LaunchpadAdapter, LaunchParams, LaunchResult, onchainos_bin\n\n_BSC_EXPLORER = \"https://bscscan.com/tx\"\n_FOURMEME_TRADE = \"https://four.meme/token\"\n\n\nclass FourMemeAdapter(LaunchpadAdapter):\n\n @property\n def name(self) -> str:\n return \"fourmeme\"\n\n @property\n def display_name(self) -> str:\n return \"Four.Meme\"\n\n @property\n def chain(self) -> str:\n return \"bsc\"\n\n def _fee_estimate(self, params: LaunchParams) -> float:\n return 0.015 # BNB gas\n\n async def launch(self, params: LaunchParams) -> LaunchResult:\n \"\"\"Launch a token on Four.Meme (BSC).\"\"\"\n\n if C.DRY_RUN:\n return LaunchResult(\n success=True,\n token_address=\"DRY_RUN_FOURMEME_NO_TOKEN\",\n tx_hash=\"DRY_RUN_FOURMEME_NO_TX\",\n error=\"DRY_RUN mode — no on-chain TX sent\",\n )\n\n factory = C.FOURMEME_FACTORY\n if not factory:\n return LaunchResult(\n success=False,\n error=\"FOURMEME_FACTORY address not configured in config.py. \"\n \"Set the Four.Meme factory contract address.\",\n )\n\n category = params.extras.get(\"category\", C.FOURMEME_CATEGORY)\n gas_price = params.extras.get(\"gas_price\", C.FOURMEME_GAS_PRICE)\n\n buy_wei = int(params.buy_amount * 10**18) if params.buy_amount > 0 else 0\n\n # ABI-encode createToken(string,string,string,string) call data\n input_data = self._encode_create_token(\n params.name, params.symbol, params.metadata_uri, category,\n )\n\n print(\" [Four.Meme] Calling factory contract...\")\n\n cmd = [\n onchainos_bin(), \"wallet\", \"contract-call\",\n \"--chain\", \"56\",\n \"--to\", factory,\n \"--input-data\", input_data,\n \"--biz-type\", \"dex\",\n \"--strategy\", \"one-click-token-launch\",\n ]\n\n if buy_wei > 0:\n cmd.extend([\"--amt\", str(buy_wei)])\n\n try:\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, stderr = await proc.communicate()\n\n if proc.returncode != 0:\n err = stderr.decode().strip() if stderr else \"unknown error\"\n return LaunchResult(\n success=False,\n error=f\"Four.Meme contract-call failed: {err}\",\n )\n\n output = json.loads(stdout.decode())\n tx_hash = output.get(\"data\", {}).get(\"txHash\", \"\") or output.get(\"txHash\", \"\")\n\n except Exception as e:\n return LaunchResult(success=False, error=f\"Four.Meme launch error: {e}\")\n\n if not tx_hash:\n return LaunchResult(success=False, error=\"No tx hash returned from contract-call\")\n\n # ── Wait for confirmation (~3-5s on BSC) ──────────────────────\n print(f\" [Four.Meme] TX submitted: {tx_hash}\")\n confirmed, token_address = await self._wait_and_parse(tx_hash, params.wallet_address)\n\n return LaunchResult(\n success=confirmed,\n token_address=token_address,\n tx_hash=tx_hash,\n explorer_url=f\"{_BSC_EXPLORER}/{tx_hash}\",\n trade_page_url=f\"{_FOURMEME_TRADE}/{token_address}\" if token_address else \"\",\n error=\"\" if confirmed else \"Transaction not confirmed within timeout\",\n )\n\n @staticmethod\n def _encode_create_token(name: str, symbol: str, metadata_uri: str, category: str) -> str:\n \"\"\"ABI-encode createToken(string,string,string,string) call data.\"\"\"\n # Function selector: keccak256(\"createToken(string,string,string,string)\")[:4]\n selector = \"a0769659\"\n\n def _encode_string(s: str) -> str:\n data = s.encode(\"utf-8\")\n length = len(data)\n # 32-byte length prefix + data padded to 32-byte boundary\n padded_len = ((length + 31) // 32) * 32\n return (\n length.to_bytes(32, \"big\").hex()\n + data.hex().ljust(padded_len * 2, \"0\")\n )\n\n # 4 dynamic params → 4 offset pointers, then data\n strings = [name, symbol, metadata_uri, category]\n encoded_strings = [_encode_string(s) for s in strings]\n\n # Calculate offsets (each pointer is 32 bytes = 64 hex chars)\n base_offset = len(strings) * 32 # offset area size in bytes\n offsets = []\n running = base_offset\n for es in encoded_strings:\n offsets.append(running.to_bytes(32, \"big\").hex())\n running += len(es) // 2 # bytes = hex chars / 2\n\n return \"0x\" + selector + \"\".join(offsets) + \"\".join(encoded_strings)\n\n async def _wait_and_parse(self, tx_hash: str, wallet_address: str = \"\", max_retries: int = 6) -> tuple:\n \"\"\"Wait for BSC TX confirmation and parse token address from logs.\n\n Returns (confirmed: bool, token_address: str).\n \"\"\"\n for i in range(max_retries):\n await asyncio.sleep(3) # BSC is ~3s blocks\n try:\n cmd = [\n onchainos_bin(), \"wallet\", \"history\",\n \"--chain\", \"56\",\n \"--tx-hash\", tx_hash,\n ]\n if wallet_address:\n cmd.extend([\"--address\", wallet_address])\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, _ = await proc.communicate()\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n status = data.get(\"status\", \"\") or data.get(\"txStatus\", \"\")\n\n if status in (\"confirmed\", \"finalized\", \"success\", \"1\"):\n # Try to extract token address from logs\n token_addr = \"\"\n logs = data.get(\"logs\", [])\n for log in logs:\n # TokenCreated event contains the new token address\n topics = log.get(\"topics\", [])\n if len(topics) >= 2:\n addr = log.get(\"address\", \"\")\n if addr and addr != C.FOURMEME_FACTORY:\n token_addr = addr\n break\n\n if not token_addr:\n token_addr = data.get(\"contractAddress\", \"\")\n\n print(f\" [Four.Meme] Confirmed! Token: {token_addr or 'parsing...'}\")\n return True, token_addr\n\n except Exception:\n pass\n\n return False, \"\"\n","content_type":"text/x-python; charset=utf-8","language":"python","size":7498,"content_sha256":"03b4b828c9c8a7c550faa20adf74cbf323c24e90eff9117b9ef38c69281b1343"},{"filename":"launchpads/letsbonk.py","content":"\"\"\"\n一键发币 v1.0 — LetsBonk adapter.\n\nFlow:\n 1. Create token via LetsBonk API (or PumpPortal with pool=\"bonk\")\n 2. Optional bundled initial buy\n 3. Sign via onchainos wallet\n 4. Submit and wait for confirmation\n\nLetsBonk has two integration paths:\n A. Native LetsBonk API (if available)\n B. PumpPortal with pool=\"bonk\" (fallback — proven to work)\n\nWe implement Path B as the primary path since PumpPortal is well-documented\nand supports LetsBonk pools via the `pool` parameter.\n\nRef: https://github.com/letsbonk-ai/bonk-mcp\n\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport json\n\nimport httpx\n\nimport config as C\nfrom .base import LaunchpadAdapter, LaunchParams, LaunchResult, onchainos_bin\n\n_SOLANA_EXPLORER = \"https://solscan.io/tx\"\n_LETSBONK_TRADE = \"https://letsbonk.fun/token\"\n\n\nclass LetsBonkAdapter(LaunchpadAdapter):\n\n @property\n def name(self) -> str:\n return \"letsbonk\"\n\n @property\n def display_name(self) -> str:\n return \"LetsBonk\"\n\n @property\n def chain(self) -> str:\n return \"solana\"\n\n def _fee_estimate(self, params: LaunchParams) -> float:\n pf = params.extras.get(\"priority_fee\", C.LETSBONK_PRIORITY_FEE)\n return pf + 0.01 # priority + rent/fees\n\n async def launch(self, params: LaunchParams) -> LaunchResult:\n \"\"\"Launch a token on LetsBonk via PumpPortal (pool=bonk).\"\"\"\n\n if C.DRY_RUN:\n return LaunchResult(\n success=True,\n token_address=\"DRY_RUN_BONK_NO_TOKEN\",\n tx_hash=\"DRY_RUN_BONK_NO_TX\",\n error=\"DRY_RUN mode — no on-chain TX sent\",\n )\n\n # ── 1. Generate mint keypair ──────────────────────────────────\n mint_keypair = await self._generate_mint_keypair()\n mint_pubkey = mint_keypair[\"pubkey\"]\n mint_secret = mint_keypair[\"secret\"]\n\n print(f\" [LetsBonk] Mint address: {mint_pubkey}\")\n\n # ── 2. Build create TX via PumpPortal (pool=bonk) ─────────────\n priority_fee = params.extras.get(\"priority_fee\", C.LETSBONK_PRIORITY_FEE)\n\n create_payload = {\n \"publicKey\": params.wallet_address,\n \"action\": \"create\",\n \"tokenMetadata\": {\n \"name\": params.name,\n \"symbol\": params.symbol,\n \"uri\": params.metadata_uri,\n },\n \"mint\": mint_secret,\n \"denominatedInSol\": \"true\",\n \"amount\": params.buy_amount,\n \"slippage\": params.slippage_bps / 100,\n \"priorityFee\": priority_fee,\n \"pool\": \"bonk\", # This routes to LetsBonk instead of pump.fun\n }\n\n async with httpx.AsyncClient(timeout=30) as client:\n resp = await client.post(\n f\"{C.PUMPFUN_API_BASE}/api/trade-local\",\n json=create_payload,\n )\n\n if resp.status_code != 200:\n return LaunchResult(\n success=False,\n error=f\"PumpPortal API error {resp.status_code}: {resp.text}\",\n )\n\n tx_data = resp.content\n\n # ── 3. Sign and submit ────────────────────────────────────────\n print(\" [LetsBonk] Signing and submitting...\")\n tx_hash = await self._sign_and_submit(tx_data, params.wallet_address, mint_pubkey)\n\n if not tx_hash:\n return LaunchResult(\n success=False,\n error=\"Failed to sign/submit via onchainos wallet\",\n )\n\n # ── 4. Wait for confirmation ──────────────────────────────────\n print(f\" [LetsBonk] TX submitted: {tx_hash}\")\n confirmed = await self._wait_confirmation(tx_hash, params.wallet_address)\n\n return LaunchResult(\n success=confirmed,\n token_address=mint_pubkey,\n tx_hash=tx_hash,\n explorer_url=f\"{_SOLANA_EXPLORER}/{tx_hash}\",\n trade_page_url=f\"{_LETSBONK_TRADE}/{mint_pubkey}\",\n error=\"\" if confirmed else \"Transaction not confirmed within timeout\",\n )\n\n async def _generate_mint_keypair(self) -> dict:\n \"\"\"Generate a random Solana Ed25519 keypair.\"\"\"\n try:\n from solders.keypair import Keypair as SoldersKeypair\n kp = SoldersKeypair()\n return {\"pubkey\": str(kp.pubkey()), \"secret\": str(kp)}\n except ImportError:\n pass\n try:\n from nacl.signing import SigningKey\n import base58\n sk = SigningKey.generate()\n full_key = sk.encode() + sk.verify_key.encode()\n return {\n \"pubkey\": base58.b58encode(sk.verify_key.encode()).decode(),\n \"secret\": base58.b58encode(full_key).decode(),\n }\n except ImportError:\n raise RuntimeError(\"Install solders or pynacl+base58 for keypair generation\")\n\n async def _sign_and_submit(self, tx_data: bytes, wallet_address: str, mint_pubkey: str = \"\") -> str:\n \"\"\"Sign and submit unsigned TX via onchainos TEE wallet.\"\"\"\n import base58 as b58\n tx_b58 = b58.b58encode(tx_data).decode()\n try:\n cmd = [\n onchainos_bin(), \"wallet\", \"contract-call\",\n \"--chain\", \"501\",\n \"--to\", mint_pubkey or wallet_address,\n \"--unsigned-tx\", tx_b58,\n \"--biz-type\", \"dex\",\n \"--strategy\", \"one-click-token-launch\",\n ]\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, stderr = await proc.communicate()\n if proc.returncode != 0:\n print(f\" [LetsBonk] contract-call failed: {stderr.decode().strip()}\")\n return \"\"\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n return data.get(\"txHash\", \"\") or output.get(\"txHash\", \"\")\n except Exception as e:\n print(f\" [LetsBonk] Sign/submit error: {e}\")\n return \"\"\n\n async def _wait_confirmation(self, tx_hash: str, wallet_address: str, max_retries: int = 5) -> bool:\n \"\"\"Poll for TX confirmation via onchainos wallet history.\"\"\"\n for i in range(max_retries):\n await asyncio.sleep(5)\n try:\n proc = await asyncio.create_subprocess_exec(\n onchainos_bin(), \"wallet\", \"history\",\n \"--chain\", \"501\",\n \"--tx-hash\", tx_hash,\n \"--address\", wallet_address,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, _ = await proc.communicate()\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n status = data.get(\"status\", \"\") or data.get(\"txStatus\", \"\")\n if status in (\"confirmed\", \"finalized\", \"success\"):\n print(f\" [LetsBonk] Confirmed! ({i + 1} polls)\")\n return True\n except Exception:\n pass\n return False\n","content_type":"text/x-python; charset=utf-8","language":"python","size":7617,"content_sha256":"24aed58812f5ae2f99fb472c345b3661315a8e20144abe5df7500aa10fff337b"},{"filename":"launchpads/moonit.py","content":"\"\"\"\n一键发币 v1.0 — Moonit adapter (Official SDK / REST API).\n\nFlow:\n 1. Prepare mint TX via Moonit API (prepareMintTx equivalent)\n 2. Sign via onchainos wallet\n 3. Submit via Moonit API (submitMintTx equivalent)\n 4. Wait for confirmation\n\nSDK ref: https://github.com/gomoonit/moonit-sdk\n\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport json\n\nimport httpx\n\nimport config as C\nfrom .base import LaunchpadAdapter, LaunchParams, LaunchResult, onchainos_bin\n\n_SOLANA_EXPLORER = \"https://solscan.io/tx\"\n_MOONIT_TRADE = \"https://moon.it/token\"\n\n\nclass MoonitAdapter(LaunchpadAdapter):\n\n @property\n def name(self) -> str:\n return \"moonit\"\n\n @property\n def display_name(self) -> str:\n return \"Moonit\"\n\n @property\n def chain(self) -> str:\n return \"solana\"\n\n def _fee_estimate(self, params: LaunchParams) -> float:\n return 0.015 # Moonit fees + rent\n\n async def launch(self, params: LaunchParams) -> LaunchResult:\n \"\"\"Launch a token on Moonit.\"\"\"\n\n if C.DRY_RUN:\n return LaunchResult(\n success=True,\n token_address=\"DRY_RUN_MOONIT_NO_TOKEN\",\n tx_hash=\"DRY_RUN_MOONIT_NO_TX\",\n error=\"DRY_RUN mode — no on-chain TX sent\",\n )\n\n api = C.MOONIT_API_BASE\n timeout = httpx.Timeout(30.0)\n migration_dex = params.extras.get(\"migration_dex\", C.MOONIT_MIGRATION_DEX)\n\n async with httpx.AsyncClient(timeout=timeout) as client:\n\n # ── 1. Prepare mint transaction ───────────────────────────\n # Moonit SDK's prepareMintTx() — we call the REST equivalent\n print(\" [Moonit] Preparing mint transaction...\")\n\n # Convert buy amount to lamports\n buy_lamports = int(params.buy_amount * 1_000_000_000) if params.buy_amount > 0 else 0\n\n prepare_resp = await client.post(\n f\"{api}/v1/token/prepare-mint\",\n json={\n \"creator\": params.wallet_address,\n \"name\": params.name,\n \"symbol\": params.symbol,\n \"metadataUri\": params.metadata_uri,\n \"migrationDex\": migration_dex,\n \"buyAmountLamports\": buy_lamports,\n \"slippageBps\": params.slippage_bps,\n },\n )\n\n if prepare_resp.status_code != 200:\n return LaunchResult(\n success=False,\n error=f\"Moonit prepare-mint failed {prepare_resp.status_code}: {prepare_resp.text}\",\n )\n\n prepare_data = prepare_resp.json()\n serialized_tx = prepare_data.get(\"transaction\", \"\")\n token_mint = prepare_data.get(\"tokenMint\", \"\")\n\n if not serialized_tx:\n return LaunchResult(\n success=False,\n error=\"Moonit returned empty transaction\",\n )\n\n print(f\" [Moonit] Token mint: {token_mint}\")\n\n # ── 2-3. Sign and broadcast via onchainos TEE wallet ─────\n print(\" [Moonit] Signing and broadcasting via TEE wallet...\")\n tx_hash = await self._sign_and_broadcast(serialized_tx, params.wallet_address, token_mint)\n\n if not tx_hash:\n return LaunchResult(\n success=False,\n error=\"Failed to submit transaction\",\n )\n\n # ── 4. Wait for confirmation ──────────────────────────────\n print(f\" [Moonit] TX submitted: {tx_hash}\")\n confirmed = await self._wait_confirmation(tx_hash, params.wallet_address)\n\n return LaunchResult(\n success=confirmed,\n token_address=token_mint,\n tx_hash=tx_hash,\n explorer_url=f\"{_SOLANA_EXPLORER}/{tx_hash}\",\n trade_page_url=f\"{_MOONIT_TRADE}/{token_mint}\",\n error=\"\" if confirmed else \"Transaction not confirmed within timeout\",\n )\n\n async def _sign_and_broadcast(self, serialized_tx: str, wallet_address: str, to_address: str = \"\") -> str:\n \"\"\"Sign unsigned TX via onchainos TEE wallet and broadcast.\"\"\"\n try:\n cmd = [\n onchainos_bin(), \"wallet\", \"contract-call\",\n \"--chain\", \"501\",\n \"--to\", to_address or wallet_address,\n \"--unsigned-tx\", serialized_tx,\n \"--biz-type\", \"dex\",\n \"--strategy\", \"one-click-token-launch\",\n ]\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, stderr = await proc.communicate()\n if proc.returncode != 0:\n print(f\" [Moonit] contract-call failed: {stderr.decode().strip()}\")\n return \"\"\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n return data.get(\"txHash\", \"\") or output.get(\"txHash\", \"\")\n except Exception as e:\n print(f\" [Moonit] Sign/broadcast error: {e}\")\n return \"\"\n\n async def _wait_confirmation(self, tx_hash: str, wallet_address: str = \"\", max_retries: int = 5) -> bool:\n \"\"\"Poll for TX confirmation via onchainos wallet history.\"\"\"\n for i in range(max_retries):\n await asyncio.sleep(5)\n try:\n cmd = [\n onchainos_bin(), \"wallet\", \"history\",\n \"--chain\", \"501\",\n \"--tx-hash\", tx_hash,\n ]\n if wallet_address:\n cmd.extend([\"--address\", wallet_address])\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, _ = await proc.communicate()\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n status = data.get(\"status\", \"\") or data.get(\"txStatus\", \"\")\n if status in (\"confirmed\", \"finalized\", \"success\"):\n print(f\" [Moonit] Confirmed! ({i + 1} polls)\")\n return True\n except Exception:\n pass\n return False\n","content_type":"text/x-python; charset=utf-8","language":"python","size":6757,"content_sha256":"377ad6456991282045a885d314999bf59a80f30413ea69108915226a843b9a37"},{"filename":"launchpads/pumpfun.py","content":"\"\"\"\nOne-click token launch v1.0 -- pump.fun adapter (via PumpPortal API).\n\nFlow:\n 1. Generate mint keypair (protocol requirement)\n 2. Get unsigned TX from PumpPortal /api/trade-local\n - User wallet is fee payer (publicKey)\n - Mint keypair secret is passed so PumpPortal includes its signature\n 3. Sign via onchainos TEE wallet (adds fee payer signature)\n 4. Wait for confirmation\n\nAll signing goes through onchainos Agentic Wallet (TEE).\nNo local private key signing of the full transaction.\n\nPumpPortal docs: https://pumpportal.fun/creation/\n\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport json\n\nimport httpx\n\nimport config as C\nfrom .base import LaunchpadAdapter, LaunchParams, LaunchResult, onchainos_bin\n\n_SOLANA_EXPLORER = \"https://solscan.io/tx\"\n_PUMPFUN_TRADE = \"https://pump.fun\"\n\n\nclass PumpFunAdapter(LaunchpadAdapter):\n\n @property\n def name(self) -> str:\n return \"pumpfun\"\n\n @property\n def display_name(self) -> str:\n return \"pump.fun\"\n\n @property\n def chain(self) -> str:\n return \"solana\"\n\n def _fee_estimate(self, params: LaunchParams) -> float:\n pf = params.extras.get(\"priority_fee\", C.PUMPFUN_PRIORITY_FEE)\n return pf + 0.015 # priority fee + rent\n\n async def launch(self, params: LaunchParams) -> LaunchResult:\n \"\"\"Launch a token on pump.fun via PumpPortal.\"\"\"\n\n if C.DRY_RUN:\n return LaunchResult(\n success=True,\n token_address=\"DRY_RUN_NO_TOKEN_ADDRESS\",\n tx_hash=\"DRY_RUN_NO_TX_HASH\",\n error=\"DRY_RUN mode -- no on-chain TX sent\",\n )\n\n from solders.keypair import Keypair as SoldersKeypair\n\n # -- 1. Generate mint keypair (pump.fun protocol requirement) ------\n mint_kp = SoldersKeypair()\n mint_pubkey = str(mint_kp.pubkey())\n mint_secret = str(mint_kp) # base58 full keypair for PumpPortal\n\n print(f\" [pump.fun] Mint address: {mint_pubkey}\")\n\n # -- 2. Get unsigned TX from PumpPortal ---------------------------\n pool = params.extras.get(\"pool\", C.PUMPFUN_POOL)\n priority_fee = params.extras.get(\"priority_fee\", C.PUMPFUN_PRIORITY_FEE)\n\n # PumpPortal signs with the mint keypair when we pass the secret.\n # The returned TX only needs the fee payer (user wallet) signature.\n create_payload = {\n \"publicKey\": params.wallet_address, # user wallet = fee payer\n \"action\": \"create\",\n \"tokenMetadata\": {\n \"name\": params.name,\n \"symbol\": params.symbol,\n \"uri\": params.metadata_uri,\n },\n \"mint\": mint_secret, # full keypair -- PumpPortal adds mint signature\n \"denominatedInSol\": \"true\",\n \"amount\": params.buy_amount,\n \"slippage\": params.slippage_bps / 100,\n \"priorityFee\": priority_fee,\n \"pool\": pool,\n }\n\n async with httpx.AsyncClient(timeout=C.PUMPFUN_TX_TIMEOUT) as client:\n resp = await client.post(\n f\"{C.PUMPFUN_API_BASE}/api/trade-local\",\n headers={\"Content-Type\": \"application/json\"},\n json=create_payload,\n )\n\n if resp.status_code != 200:\n return LaunchResult(\n success=False,\n error=f\"PumpPortal API error {resp.status_code}: {resp.text}\",\n )\n\n # PumpPortal returns the TX as bytes (or base58 string)\n tx_data = resp.content\n\n if not tx_data:\n return LaunchResult(\n success=False,\n error=\"PumpPortal returned empty transaction\",\n )\n\n # -- 3. Sign and broadcast via onchainos TEE wallet ---------------\n print(\" [pump.fun] Signing via TEE wallet...\")\n import base58\n tx_b58 = base58.b58encode(tx_data).decode()\n\n tx_hash = await self._sign_and_broadcast(\n tx_b58, params.wallet_address, mint_pubkey,\n mev_protection=params.mev_protection,\n )\n\n if not tx_hash:\n return LaunchResult(\n success=False,\n error=\"Failed to sign/broadcast via onchainos wallet\",\n )\n\n # -- 4. Wait for confirmation -------------------------------------\n print(f\" [pump.fun] TX submitted: {tx_hash}\")\n print(\" [pump.fun] Waiting for confirmation...\")\n\n confirmed = await self._wait_confirmation(tx_hash, params.wallet_address)\n\n return LaunchResult(\n success=confirmed,\n token_address=mint_pubkey,\n tx_hash=tx_hash,\n explorer_url=f\"{_SOLANA_EXPLORER}/{tx_hash}\",\n trade_page_url=f\"{_PUMPFUN_TRADE}/{mint_pubkey}\",\n error=\"\" if confirmed else \"Transaction not confirmed within timeout\",\n )\n\n async def _sign_and_broadcast(\n self, unsigned_tx_b58: str, wallet_address: str,\n to_address: str = \"\", mev_protection: bool = False,\n ) -> str:\n \"\"\"Sign unsigned TX via onchainos TEE wallet and broadcast.\n\n Returns TX hash on success, empty string on failure.\n \"\"\"\n try:\n cmd = [\n onchainos_bin(), \"wallet\", \"contract-call\",\n \"--chain\", \"501\",\n \"--to\", to_address or wallet_address,\n \"--unsigned-tx\", unsigned_tx_b58,\n ]\n if mev_protection:\n cmd.append(\"--mev-protection\")\n cmd.extend([\"--jito-unsigned-tx\", unsigned_tx_b58])\n\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, stderr = await proc.communicate()\n\n if proc.returncode != 0:\n err = stderr.decode().strip() if stderr else \"unknown error\"\n print(f\" [pump.fun] contract-call failed: {err}\")\n return \"\"\n\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n return data.get(\"txHash\", \"\") or output.get(\"txHash\", \"\")\n\n except Exception as e:\n print(f\" [pump.fun] Sign/broadcast error: {e}\")\n return \"\"\n\n async def _wait_confirmation(\n self, tx_hash: str, wallet_address: str, max_retries: int = 15,\n ) -> bool:\n \"\"\"Poll for TX confirmation via onchainos wallet history.\"\"\"\n delays = [0.5, 0.5, 1, 1] + [2] * (max_retries - 4)\n for i in range(max_retries):\n await asyncio.sleep(delays[i] if i \u003c len(delays) else 2)\n try:\n cmd = [\n onchainos_bin(), \"wallet\", \"history\",\n \"--chain\", \"501\",\n \"--tx-hash\", tx_hash,\n \"--address\", wallet_address,\n ]\n proc = await asyncio.create_subprocess_exec(\n *cmd,\n stdout=asyncio.subprocess.PIPE,\n stderr=asyncio.subprocess.PIPE,\n )\n stdout, _ = await proc.communicate()\n output = json.loads(stdout.decode())\n data = output.get(\"data\", {})\n if isinstance(data, list) and data:\n data = data[0]\n status = data.get(\"status\", \"\") or data.get(\"txStatus\", \"\")\n\n if status in (\"confirmed\", \"finalized\", \"success\"):\n print(f\" [pump.fun] {status.capitalize()}! ({i + 1} polls)\")\n return True\n if \"fail\" in str(status).lower() or \"error\" in str(status).lower():\n print(f\" [pump.fun] TX failed: {status}\")\n return False\n except Exception:\n pass\n if (i + 1) % 3 == 0:\n print(f\" [pump.fun] Waiting for confirmation... ({i + 1}/{max_retries})\")\n return False\n","content_type":"text/x-python; charset=utf-8","language":"python","size":8055,"content_sha256":"96f76b80b31476153064fa2487b501d17e77a4d868c7fa219f4b883e2861ea6e"},{"filename":"plugin.yaml","content":"schema_version: 1\nname: one-click-token-launch\nversion: \"1.0.0\"\ndescription: \"One-click multi-launchpad token creation with bundled buy, IPFS metadata, MEV protection across 6 launchpads on Solana and BSC\"\nauthor:\n name: \"victorlee\"\n github: \"VibeCodeDaddy69\"\nlicense: MIT\ncategory: strategy\ntags:\n - solana\n - bsc\n - onchainos\n - one-click-token-launch\n - meme-coin\n\ncomponents:\n skill:\n dir: \".\"\n\napi_calls:\n - pumpportal.fun\n - pump.fun\n - bags.fm\n - moon.it\n - mainnet.block-engine.jito.wtf\n - api.pinata.cloud\n - gateway.pinata.cloud\n - solscan.io\n - bscscan.com\ntype: community-developer\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":614,"content_sha256":"6313eb941ba1001e5ac4a4a4be5f62fcf66284420377cc9c2981d2c781cc4c8d"},{"filename":"post_launch.py","content":"\"\"\"\n一键发币 v1.0 — Post-Launch Monitor\n\nShows live token stats in terminal after launch:\n - Bonding curve progress (pump.fun)\n - Price, market cap, volume\n - Holder count + distribution\n - Buy/sell activity\n - Auto-refresh every N seconds\n\nUsage:\n from post_launch import monitor\n await monitor(\"8BWqp55pn6GvpKFZkqAAeytc5H6P7KBNpStMaRUz3a8U\")\n\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport json\nimport os\nimport subprocess\nimport sys\nimport time\nfrom concurrent.futures import ThreadPoolExecutor\nfrom datetime import datetime, timezone\nfrom pathlib import Path\nfrom typing import Optional\n\n# Ensure skill directory is on sys.path\n_SKILL_DIR = str(Path(__file__).resolve().parent)\nif _SKILL_DIR not in sys.path:\n sys.path.insert(0, _SKILL_DIR)\n\nfrom launchpads.base import onchainos_bin\n_ONCHAINOS = onchainos_bin()\n\n_SOLANA_EXPLORER = \"https://solscan.io/tx\"\n_PUMPFUN_TRADE = \"https://pump.fun\"\n\n# ── Box drawing chars ────────────────────────────────────────────────\n_TL = \"╔\"; _TR = \"╗\"; _BL = \"╚\"; _BR = \"╝\"\n_H = \"═\"; _V = \"║\"; _ML = \"╠\"; _MR = \"╣\"\n\n# ── ANSI colors ──────────────────────────────────────────────────────\n_RESET = \"\\033[0m\"\n_BOLD = \"\\033[1m\"\n_DIM = \"\\033[2m\"\n_GREEN = \"\\033[32m\"\n_RED = \"\\033[31m\"\n_CYAN = \"\\033[36m\"\n_YELLOW = \"\\033[33m\"\n_WHITE = \"\\033[37m\"\n_BG_GREEN = \"\\033[42m\"\n_BG_DIM = \"\\033[48;5;236m\"\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Data Fetching (via onchainos CLI)\n# ══════════════════════════════════════════════════════════════════════\n\ndef _cli(*args: str, timeout: int = 15) -> dict:\n \"\"\"Run onchainos CLI and return parsed JSON.\"\"\"\n cmd = [_ONCHAINOS] + list(args)\n try:\n proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)\n if proc.returncode != 0:\n return {}\n return json.loads(proc.stdout)\n except Exception:\n return {}\n\n\ndef fetch_memepump_details(token: str, chain: str = \"501\", wallet: str = \"\") -> dict:\n \"\"\"Fetch pump.fun token details (bonding curve, holders, market data).\"\"\"\n args = [\"memepump\", \"token-details\", \"--chain\", chain, \"--address\", token]\n if wallet:\n args.extend([\"--wallet\", wallet])\n result = _cli(*args)\n return result.get(\"data\", {}) if result else {}\n\n\ndef fetch_price_info(token: str, chain: str = \"501\") -> dict:\n \"\"\"Fetch detailed price info (price, mcap, volume, change).\"\"\"\n result = _cli(\"token\", \"price-info\", \"--chain\", chain, \"--address\", token, timeout=10)\n return result.get(\"data\", {}) if result else {}\n\n\ndef fetch_holders(token: str, chain: str = \"501\") -> list:\n \"\"\"Fetch top holders list.\"\"\"\n result = _cli(\"token\", \"holders\", \"--chain\", chain, \"--address\", token)\n data = result.get(\"data\", [])\n return data if isinstance(data, list) else []\n\n\ndef fetch_trades(token: str, chain: str = \"501\", limit: int = 20) -> list:\n \"\"\"Fetch recent trades.\"\"\"\n result = _cli(\"token\", \"trades\", \"--chain\", chain, \"--address\", token, \"--limit\", str(limit))\n data = result.get(\"data\", [])\n return data if isinstance(data, list) else []\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Stats Aggregation\n# ══════════════════════════════════════════════════════════════════════\n\ndef gather_stats(token: str, chain: str = \"501\", launchpad: str = \"pumpfun\", wallet: str = \"\") -> dict:\n \"\"\"Gather all stats for a token. Returns a flat dict.\"\"\"\n stats = {\n \"token\": token,\n \"chain\": chain,\n \"launchpad\": launchpad,\n \"name\": \"\",\n \"symbol\": \"\",\n \"bonding_pct\": None,\n \"price_usd\": None,\n \"mcap_usd\": None,\n \"volume_1h\": None,\n \"buy_count_1h\": None,\n \"sell_count_1h\": None,\n \"tx_count_1h\": None,\n \"holders\": None,\n \"top10_pct\": None,\n \"bundler_pct\": None,\n \"sniper_pct\": None,\n \"dev_hold_pct\": None,\n \"fresh_wallet_pct\": None,\n \"creator\": \"\",\n \"created_ts\": None,\n \"status\": \"UNKNOWN\",\n \"twitter\": \"\",\n \"telegram\": \"\",\n \"website\": \"\",\n \"dexscreener_paid\": False,\n \"cto\": False,\n \"top_holders\": [],\n \"recent_trades\": [],\n \"fetch_time\": time.time(),\n }\n\n # ── Fetch all data concurrently ──────────────────────────────────\n is_pump = launchpad in (\"pumpfun\", \"letsbonk\")\n\n with ThreadPoolExecutor(max_workers=4) as pool:\n f_details = pool.submit(fetch_memepump_details, token, chain, wallet) if is_pump else None\n f_price = pool.submit(fetch_price_info, token, chain)\n f_holders = pool.submit(fetch_holders, token, chain)\n f_trades = pool.submit(fetch_trades, token, chain, 10)\n\n details = f_details.result() if f_details else {}\n price_data = f_price.result()\n holders_list = f_holders.result()\n trades = f_trades.result()\n\n # ── Pump.fun details (primary data source) ──────────────────────\n if is_pump and details:\n stats[\"name\"] = details.get(\"name\", \"\")\n stats[\"symbol\"] = details.get(\"symbol\", \"\")\n stats[\"creator\"] = details.get(\"creatorAddress\", \"\")\n\n bp = details.get(\"bondingPercent\", \"\")\n stats[\"bonding_pct\"] = float(bp) if bp else 0.0\n\n ts = details.get(\"createdTimestamp\", \"\")\n stats[\"created_ts\"] = int(ts) / 1000 if ts else None\n\n # Market data\n mkt = details.get(\"market\", {})\n mcap = mkt.get(\"marketCapUsd\", \"\")\n stats[\"mcap_usd\"] = float(mcap) if mcap else None\n vol = mkt.get(\"volumeUsd1h\", \"\")\n stats[\"volume_1h\"] = float(vol) if vol else None\n bc = mkt.get(\"buyTxCount1h\", \"\")\n stats[\"buy_count_1h\"] = int(bc) if bc else 0\n sc = mkt.get(\"sellTxCount1h\", \"\")\n stats[\"sell_count_1h\"] = int(sc) if sc else 0\n tc = mkt.get(\"txCount1h\", \"\")\n stats[\"tx_count_1h\"] = int(tc) if tc else 0\n\n # Tags\n tags = details.get(\"tags\", {})\n h = tags.get(\"totalHolders\", \"\")\n stats[\"holders\"] = int(h) if h else 0\n t10 = tags.get(\"top10HoldingsPercent\", \"\")\n stats[\"top10_pct\"] = float(t10) if t10 else 0.0\n stats[\"bundler_pct\"] = float(tags.get(\"bundlersPercent\", \"0\") or \"0\")\n stats[\"sniper_pct\"] = float(tags.get(\"snipersPercent\", \"0\") or \"0\")\n stats[\"dev_hold_pct\"] = float(tags.get(\"devHoldingsPercent\", \"0\") or \"0\")\n stats[\"fresh_wallet_pct\"] = float(tags.get(\"freshWalletsPercent\", \"0\") or \"0\")\n\n # Social\n social = details.get(\"social\", {})\n stats[\"twitter\"] = social.get(\"x\", \"\")\n stats[\"telegram\"] = social.get(\"telegram\", \"\")\n stats[\"website\"] = social.get(\"website\", \"\")\n stats[\"dexscreener_paid\"] = social.get(\"dexScreenerPaid\", False)\n stats[\"cto\"] = social.get(\"communityTakeover\", False)\n\n # Status\n migrated_end = details.get(\"migratedEndTimestamp\", \"\")\n migrating = details.get(\"migratedBeginTimestamp\", \"\")\n if migrated_end:\n stats[\"status\"] = \"MIGRATED\"\n elif migrating:\n stats[\"status\"] = \"MIGRATING\"\n elif stats[\"bonding_pct\"] is not None:\n stats[\"status\"] = \"BONDING CURVE\"\n else:\n stats[\"status\"] = \"ACTIVE\"\n\n # ── Price info (supplement) ─────────────────────────────────────\n if price_data:\n p = price_data.get(\"price\", \"\") or price_data.get(\"priceUsd\", \"\")\n if p:\n stats[\"price_usd\"] = float(p)\n if not stats[\"mcap_usd\"]:\n m = price_data.get(\"marketCapUsd\", \"\") or price_data.get(\"marketCap\", \"\")\n if m:\n stats[\"mcap_usd\"] = float(m)\n if not stats[\"name\"]:\n stats[\"name\"] = price_data.get(\"tokenName\", \"\") or price_data.get(\"name\", \"\")\n if not stats[\"symbol\"]:\n stats[\"symbol\"] = price_data.get(\"tokenSymbol\", \"\") or price_data.get(\"symbol\", \"\")\n\n # ── Holders (supplement — count from list if needed) ────────────\n if holders_list:\n stats[\"top_holders\"] = holders_list[:5]\n if not stats[\"holders\"] or stats[\"holders\"] \u003c len(holders_list):\n stats[\"holders\"] = max(stats[\"holders\"] or 0, len(holders_list))\n\n # ── Recent trades ───────────────────────────────────────────────\n if trades:\n stats[\"recent_trades\"] = trades[:5]\n\n return stats\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Terminal Display\n# ══════════════════════════════════════════════════════════════════════\n\ndef _bar(pct: float, width: int = 20) -> str:\n \"\"\"Render a progress bar.\"\"\"\n pct = max(0.0, min(pct, 100.0))\n filled = int(pct / 100 * width)\n empty = width - filled\n bar = f\"{_BG_GREEN}{_WHITE}\" + \"█\" * filled + f\"{_RESET}\"\n bar += f\"{_BG_DIM}\" + \"░\" * empty + f\"{_RESET}\"\n return bar\n\n\ndef _fmt_usd(val: Optional[float]) -> str:\n if val is None:\n return f\"{_DIM}—{_RESET}\"\n if val >= 1_000_000:\n return f\"${val / 1_000_000:.2f}M\"\n if val >= 1_000:\n return f\"${val / 1_000:.1f}K\"\n if val >= 1:\n return f\"${val:.2f}\"\n return f\"${val:.6f}\"\n\n\ndef _fmt_price(val: Optional[float]) -> str:\n if val is None:\n return f\"{_DIM}—{_RESET}\"\n if val >= 1:\n return f\"${val:.4f}\"\n if val >= 0.0001:\n return f\"${val:.6f}\"\n return f\"${val:.10f}\"\n\n\ndef _fmt_pct(val: Optional[float]) -> str:\n if val is None:\n return f\"{_DIM}—{_RESET}\"\n return f\"{val:.1f}%\"\n\n\ndef _fmt_int(val: Optional[int]) -> str:\n if val is None or val == 0:\n return f\"{_DIM}0{_RESET}\"\n if val >= 1_000_000:\n return f\"{val / 1_000_000:.1f}M\"\n if val >= 1_000:\n return f\"{val / 1_000:.1f}K\"\n return str(val)\n\n\ndef _age_str(created_ts: Optional[float]) -> str:\n if not created_ts:\n return f\"{_DIM}—{_RESET}\"\n elapsed = time.time() - created_ts\n if elapsed \u003c 0:\n return \"just now\"\n if elapsed \u003c 60:\n return f\"{int(elapsed)}s\"\n if elapsed \u003c 3600:\n m = int(elapsed // 60)\n s = int(elapsed % 60)\n return f\"{m}m {s}s\"\n if elapsed \u003c 86400:\n h = int(elapsed // 3600)\n m = int((elapsed % 3600) // 60)\n return f\"{h}h {m}m\"\n d = int(elapsed // 86400)\n h = int((elapsed % 86400) // 3600)\n return f\"{d}d {h}h\"\n\n\ndef _status_color(status: str) -> str:\n if status == \"MIGRATED\":\n return _GREEN\n if status == \"MIGRATING\":\n return _YELLOW\n if status == \"BONDING CURVE\":\n return _CYAN\n return _WHITE\n\n\ndef _short(addr: str, n: int = 6) -> str:\n if len(addr) \u003c= n * 2 + 3:\n return addr\n return f\"{addr[:n]}...{addr[-4:]}\"\n\n\ndef render(stats: dict, width: int = 58) -> str:\n \"\"\"Render stats into a terminal box string.\"\"\"\n inner = width - 4 # space inside box (between ║ and ║)\n\n name = stats.get(\"name\", \"?\")\n symbol = stats.get(\"symbol\", \"?\")\n token = stats.get(\"token\", \"\")\n launchpad = stats.get(\"launchpad\", \"\")\n\n lp_display = {\n \"pumpfun\": \"pump.fun\", \"letsbonk\": \"LetsBonk\", \"bags\": \"Bags.fm\",\n \"moonit\": \"Moonit\", \"fourmeme\": \"Four.Meme\", \"flap\": \"Flap.sh\",\n }.get(launchpad, launchpad)\n\n lines = []\n\n # Top border\n lines.append(f\"{_CYAN}{_TL}{_H * (width - 2)}{_TR}{_RESET}\")\n\n # Title\n title = f\" {_BOLD}{name}{_RESET} ({symbol}) — {lp_display}\"\n lines.append(f\"{_CYAN}{_V}{_RESET}{title:\u003c{inner + 20}}{_CYAN}{_V}{_RESET}\")\n\n # Token address\n addr_line = f\" {_DIM}{token}{_RESET}\"\n lines.append(f\"{_CYAN}{_V}{_RESET}{addr_line:\u003c{inner + 12}}{_CYAN}{_V}{_RESET}\")\n\n # Separator\n lines.append(f\"{_CYAN}{_ML}{_H * (width - 2)}{_MR}{_RESET}\")\n\n # ── Stats rows ──────────────────────────────────────────────────\n def row(label: str, value: str, label_w: int = 16):\n padded_label = f\" {label:\u003c{label_w}}\"\n return f\"{_CYAN}{_V}{_RESET}{padded_label}{value}\"\n\n # Bonding curve (pump.fun only)\n bp = stats.get(\"bonding_pct\")\n if bp is not None:\n bar = _bar(bp)\n bp_str = f\"{bp:.1f}%\"\n color = _GREEN if bp >= 80 else _YELLOW if bp >= 50 else _WHITE\n lines.append(row(\"Bonding Curve\", f\"{bar} {color}{bp_str}{_RESET}\"))\n\n # Price\n lines.append(row(\"Price\", f\"{_BOLD}{_fmt_price(stats.get('price_usd'))}{_RESET}\"))\n\n # Market cap\n lines.append(row(\"Market Cap\", _fmt_usd(stats.get(\"mcap_usd\"))))\n\n # Volume\n lines.append(row(\"Volume (1h)\", _fmt_usd(stats.get(\"volume_1h\"))))\n\n # Holders\n h = stats.get(\"holders\")\n lines.append(row(\"Holders\", f\"{_BOLD}{_fmt_int(h)}{_RESET}\"))\n\n # Buy / Sell\n buys = stats.get(\"buy_count_1h\", 0) or 0\n sells = stats.get(\"sell_count_1h\", 0) or 0\n bs = f\"{_GREEN}{buys}{_RESET} / {_RED}{sells}{_RESET}\"\n lines.append(row(\"Buys / Sells\", bs))\n\n # Top 10 %\n lines.append(row(\"Top 10 Hold%\", _fmt_pct(stats.get(\"top10_pct\"))))\n\n # Dev holdings\n dev = stats.get(\"dev_hold_pct\") or 0\n dev_color = _RED if dev > 5 else _YELLOW if dev > 2 else _GREEN\n lines.append(row(\"Dev Holdings\", f\"{dev_color}{_fmt_pct(dev)}{_RESET}\"))\n\n # Bundlers + Snipers\n bund = stats.get(\"bundler_pct\") or 0\n snip = stats.get(\"sniper_pct\") or 0\n if bund or snip:\n warn_color = _RED if (bund > 10 or snip > 10) else _YELLOW\n lines.append(row(\"Bundle / Sniper\", f\"{warn_color}{_fmt_pct(bund)} / {_fmt_pct(snip)}{_RESET}\"))\n\n # Age\n lines.append(row(\"Age\", _age_str(stats.get(\"created_ts\"))))\n\n # Separator\n lines.append(f\"{_CYAN}{_ML}{_H * (width - 2)}{_MR}{_RESET}\")\n\n # Status\n status = stats.get(\"status\", \"UNKNOWN\")\n sc = _status_color(status)\n lines.append(row(\"Status\", f\"{sc}{_BOLD}{status}{_RESET}\"))\n\n # Creator\n creator = stats.get(\"creator\", \"\")\n if creator:\n lines.append(row(\"Creator\", f\"{_DIM}{_short(creator, 8)}{_RESET}\"))\n\n # Flags\n flags = []\n if stats.get(\"cto\"):\n flags.append(f\"{_YELLOW}CTO{_RESET}\")\n if stats.get(\"dexscreener_paid\"):\n flags.append(f\"{_GREEN}DS Paid{_RESET}\")\n if flags:\n lines.append(row(\"Flags\", \" \".join(flags)))\n\n # Links\n links = []\n if stats.get(\"twitter\"):\n links.append(\"X\")\n if stats.get(\"telegram\"):\n links.append(\"TG\")\n if stats.get(\"website\"):\n links.append(\"Web\")\n if links:\n lines.append(row(\"Socials\", f\"{_DIM}{' | '.join(links)}{_RESET}\"))\n\n # Bottom border\n lines.append(f\"{_CYAN}{_BL}{_H * (width - 2)}{_BR}{_RESET}\")\n\n # Trade page\n if launchpad in (\"pumpfun\", \"letsbonk\"):\n lines.append(f\" {_DIM}{_PUMPFUN_TRADE}/{token}{_RESET}\")\n\n return \"\\n\".join(lines)\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Recent Trades Display\n# ══════════════════════════════════════════════════════════════════════\n\ndef render_trades(stats: dict) -> str:\n \"\"\"Render recent trades summary.\"\"\"\n trades = stats.get(\"recent_trades\", [])\n if not trades:\n return \"\"\n\n lines = [f\"\\n {_BOLD}Recent Trades{_RESET}\"]\n for t in trades[:5]:\n side = t.get(\"tradeDirection\", t.get(\"side\", \"\"))\n is_buy = side in (\"buy\", \"1\")\n color = _GREEN if is_buy else _RED\n direction = \"BUY \" if is_buy else \"SELL\"\n\n amount = t.get(\"tradeAmount\", t.get(\"amount\", \"\"))\n price = t.get(\"price\", \"\")\n wallet = t.get(\"makerAddress\", t.get(\"traderAddress\", \"\"))\n tag = t.get(\"traderTag\", \"\")\n\n tag_str = \"\"\n tag_map = {\"1\": \"KOL\", \"2\": \"Dev\", \"3\": \"SM\", \"4\": \"Whale\", \"5\": \"New\", \"7\": \"Sniper\", \"9\": \"Bundle\"}\n if tag and tag in tag_map:\n tag_str = f\" [{tag_map[tag]}]\"\n\n lines.append(\n f\" {color}{direction}{_RESET} \"\n f\"{_short(wallet, 4)} \"\n f\"{_DIM}{tag_str}{_RESET}\"\n )\n\n return \"\\n\".join(lines)\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Top Holders Display\n# ══════════════════════════════════════════════════════════════════════\n\ndef render_holders(stats: dict) -> str:\n \"\"\"Render top holders summary.\"\"\"\n holders = stats.get(\"top_holders\", [])\n if not holders:\n return \"\"\n\n lines = [f\"\\n {_BOLD}Top Holders{_RESET}\"]\n for i, h in enumerate(holders[:5], 1):\n addr = h.get(\"holderWalletAddress\", \"\")\n pct = h.get(\"holdPercent\", \"0\")\n pnl = h.get(\"totalPnlUsd\", \"\")\n\n pct_f = float(pct) if pct else 0\n pnl_str = \"\"\n if pnl:\n pnl_f = float(pnl)\n pnl_color = _GREEN if pnl_f >= 0 else _RED\n pnl_str = f\" {pnl_color}{_fmt_usd(pnl_f)}{_RESET}\"\n\n lines.append(\n f\" {_DIM}{i}.{_RESET} {_short(addr, 6)} \"\n f\"{_BOLD}{pct_f:.1f}%{_RESET}{pnl_str}\"\n )\n\n return \"\\n\".join(lines)\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Main Monitor Loop\n# ══════════════════════════════════════════════════════════════════════\n\nasync def monitor(\n token: str,\n chain: str = \"501\",\n launchpad: str = \"pumpfun\",\n wallet: str = \"\",\n refresh: int = 10,\n max_rounds: int = 0,\n) -> None:\n \"\"\"Live post-launch monitor. Refreshes every `refresh` seconds.\n\n Args:\n token: Token contract address\n chain: Chain index (501=Solana, 56=BSC)\n launchpad: pumpfun, bags, letsbonk, moonit, fourmeme, flap\n wallet: Optional wallet address (for position/PnL tracking)\n refresh: Seconds between refreshes\n max_rounds: 0 = infinite, >0 = stop after N rounds\n \"\"\"\n round_num = 0\n\n try:\n while True:\n round_num += 1\n\n # Fetch (blocking — runs onchainos CLI calls)\n stats = await asyncio.get_event_loop().run_in_executor(\n None, gather_stats, token, chain, launchpad, wallet\n )\n\n # Clear screen\n print(\"\\033[2J\\033[H\", end=\"\")\n\n # Render\n print(render(stats))\n print(render_holders(stats))\n print(render_trades(stats))\n\n # Footer\n elapsed = time.time() - stats[\"fetch_time\"]\n print(f\"\\n {_DIM}Updated {elapsed:.0f}s ago | Refresh: {refresh}s | Ctrl+C to stop{_RESET}\")\n\n if max_rounds and round_num >= max_rounds:\n break\n\n await asyncio.sleep(refresh)\n\n except KeyboardInterrupt:\n print(f\"\\n {_DIM}Monitor stopped.{_RESET}\")\n\n\ndef monitor_sync(\n token: str,\n chain: str = \"501\",\n launchpad: str = \"pumpfun\",\n wallet: str = \"\",\n refresh: int = 10,\n max_rounds: int = 0,\n) -> None:\n \"\"\"Synchronous wrapper for monitor().\"\"\"\n asyncio.run(monitor(token, chain, launchpad, wallet, refresh, max_rounds))\n\n\n# ══════════════════════════════════════════════════════════════════════\n# One-shot snapshot (no loop)\n# ══════════════════════════════════════════════════════════════════════\n\ndef snapshot(token: str, chain: str = \"501\", launchpad: str = \"pumpfun\", wallet: str = \"\") -> dict:\n \"\"\"Fetch and display a single snapshot. Returns the stats dict.\"\"\"\n stats = gather_stats(token, chain, launchpad, wallet)\n print(render(stats))\n print(render_holders(stats))\n print(render_trades(stats))\n return stats\n\n\n# ══════════════════════════════════════════════════════════════════════\n# CLI Entry\n# ══════════════════════════════════════════════════════════════════════\n\nif __name__ == \"__main__\":\n if len(sys.argv) \u003c 2:\n print(\"Usage: python3 post_launch.py \u003ctoken_address> [--refresh 10] [--chain 501] [--launchpad pumpfun]\")\n sys.exit(1)\n\n token_addr = sys.argv[1]\n refresh_s = 10\n chain_id = \"501\"\n lp = \"pumpfun\"\n\n args = sys.argv[2:]\n i = 0\n while i \u003c len(args):\n if args[i] == \"--refresh\" and i + 1 \u003c len(args):\n refresh_s = int(args[i + 1]); i += 2\n elif args[i] == \"--chain\" and i + 1 \u003c len(args):\n chain_id = args[i + 1]; i += 2\n elif args[i] == \"--launchpad\" and i + 1 \u003c len(args):\n lp = args[i + 1]; i += 2\n else:\n i += 1\n\n monitor_sync(token_addr, chain_id, lp, refresh=refresh_s)\n","content_type":"text/x-python; charset=utf-8","language":"python","size":23015,"content_sha256":"7cc8a6071b461655a26d7088e9405f5d1d52dad3034d96c4247ab5b6fd0b7605"},{"filename":"README.md","content":"# Token Launch (一键发币)\n\nOne-click multi-launchpad token creation — supports pump.fun, Bags.fm, LetsBonk, Moonit on Solana and Four.Meme, Flap.sh on BSC. Bundled initial buy with MEV protection, IPFS metadata upload, and post-launch monitoring. All on-chain operations powered by onchainos Agentic Wallet (TEE signing, no private keys needed).\n\n一键发币工具 — 支持 pump.fun、Bags.fm、LetsBonk、Moonit(Solana)及 Four.Meme、Flap.sh(BSC)。原子捆绑买入 + Jito MEV 保护,IPFS 元数据上传,发币后实时监控。全部链上操作通过 onchainos Agentic Wallet TEE 签名,无需私钥。\n\n## Features\n\n- **6 Launchpads** — pump.fun, Bags.fm, LetsBonk, Moonit (Solana) + Four.Meme, Flap.sh (BSC)\n- **One-Call Launch** — `quick_launch()` handles wallet, IPFS, signing, broadcast automatically\n- **Bundled Buy** — Create + buy in ONE atomic Jito bundle, no front-running\n- **IPFS Upload** — pump.fun free endpoint (no API key), Pinata fallback\n- **Flexible Image Input** — File path, URL, base64, data URI\n- **MEV Protection** — Jito bundles (Solana), atomic contract calls (BSC)\n- **TEE Signing** — onchainos Agentic Wallet, private keys never leave secure enclave\n- **Paper Mode** — DRY_RUN=True by default, safe to test\n- **Web Dashboard** — Token logos, bonding curve progress, live stats at http://localhost:3245\n- **Post-Launch Monitor** — Real-time price, holders, liquidity tracking\n- **Hot-Reload** — Modify config.py without restarting\n\n## Install\n\n```bash\nnpx skills add okx/plugin-store --skill one-click-token-launch\n```\n\n## Prerequisites\n\n```bash\n# 1. onchainos CLI >= 2.1.0\nonchainos --version\n\n# 2. Login to Agentic Wallet\nonchainos wallet login \u003cyour-email>\n\n# 3. Python dependencies\npip install -r requirements.txt\n```\n\n## Risk Warning\n\n> Token creation is irreversible. Launched tokens may fail, lose all liquidity, or face regulatory scrutiny. Always test in Paper Mode (DRY_RUN=True) first. This tool is for educational and research purposes only — not investment advice.\n\n> 代币创建不可逆。已发行的代币可能失败、失去所有流动性或面临监管审查。请始终先在模拟模式 (DRY_RUN=True) 下测试。本工具仅供教育和研究用途,不构成投资建议。\n\n## License\n\nMIT\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2300,"content_sha256":"669a64a65a0a16479b6fcd42d2dd93b2a0f3298e16d8a7ab3999da7fe7decc4b"},{"filename":"requirements.txt","content":"httpx>=0.24,\u003c1.0\nbase58>=2.1,\u003c3.0\nsolders>=0.18,\u003c1.0\n","content_type":"text/plain; charset=utf-8","language":null,"size":53,"content_sha256":"6e9722aa77c55d5f62be153343f82780d6eb6cb7cb3cda8fbad7ab860d795c7b"},{"filename":"SUMMARY.md","content":"## Overview\n\nOne-Click Token Launch is a multi-launchpad token creation skill that deploys a new token with optional bundled initial buy, IPFS metadata upload, and MEV protection in a single command across 6 launchpads on Solana and BSC.\n\nCore operations:\n\n- Deploy tokens on 6 launchpads: pump.fun, LetsBonk, Bags.fm, Moonit, Four.meme, Flap.sh\n- Bundle an initial buy with the launch in one atomic transaction\n- Upload token image and description to IPFS (Pinata or pump.fun IPFS)\n- Submit via Jito bundle for MEV protection on Solana launches\n- Return explorer link (Solscan or BscScan) upon successful launch\n\nTags: `token-launch` `meme-coin` `solana` `bsc` `pump.fun` `launchpad` `onchainos`\n\n## Prerequisites\n\n- No IP/region restrictions (check local regulations on token issuance)\n- Supported chains: Solana, BSC\n- Supported launchpads: pump.fun, LetsBonk, Bags.fm, Moonit, Four.meme, Flap.sh\n- onchainos CLI installed and authenticated (`onchainos --version` and `onchainos wallet status`)\n- Python 3.8+ (standard library only — no `pip install` required)\n- Sufficient SOL or BNB for launchpad fees, initial buy, and gas\n- (Optional) Pinata API key for IPFS metadata fallback\n\n## Quick Start\n\n1. **Install the skill**: `plugin-store install one-click-token-launch`\n2. **Configure your token**: Edit `config.py` with your token name, symbol, image path, description, and initial buy amount\n3. **Launch your token**: Tell your agent \"Launch a token on pump.fun\" or \"一键发币\"\n4. **Review cost summary**: The skill shows total estimated fees (launchpad + gas + Jito) before executing\n5. **Confirm and deploy**: The skill uploads metadata to IPFS, deploys the token, and submits the bundled buy\n6. **Verify on-chain**: Check the returned Solscan or BscScan link to confirm your token is live\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1802,"content_sha256":"a8e350e56c53273a78cd51ca9a9456ac8fc4881704e80d4a1a97ddc366559b8c"},{"filename":"token_launch.py","content":"\"\"\"\n一键发币 v1.0 — One-click token launch.\n\nUsage:\n # From Claude Code or any Python caller — one call does everything:\n from token_launch import quick_launch\n result = await quick_launch(\"MoonDog\", \"MDOG\", \"a good boy\", \"/path/to/dog.png\")\n\n # With options:\n result = await quick_launch(\n \"MoonDog\", \"MDOG\", \"a good boy\", \"https://example.com/dog.png\",\n launchpad=\"pumpfun\", buy_amount=0.1,\n twitter=\"https://twitter.com/moondog\",\n )\n\n # Dashboard mode:\n python3 token_launch.py\n\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport base64 as b64\nimport json\nimport os\nimport sys\nimport subprocess\nimport tempfile\nimport time\nfrom datetime import datetime, timezone\nfrom http.server import HTTPServer, SimpleHTTPRequestHandler\nfrom pathlib import Path\nfrom threading import Lock, Thread\nfrom typing import Optional\n\n# ── Ensure skill directory is on sys.path (works from any CWD) ────────\n_SKILL_DIR = str(Path(__file__).resolve().parent)\nif _SKILL_DIR not in sys.path:\n sys.path.insert(0, _SKILL_DIR)\n\nimport config as C\nimport ipfs\nfrom launchpads import get_adapter, LaunchParams, LaunchResult\nfrom launchpads.base import onchainos_bin\n\n# ── State ─────────────────────────────────────────────────────────────\n_BASE_DIR = Path(__file__).parent\n_STATE_DIR = _BASE_DIR / \"state\"\n_LAUNCHES = _STATE_DIR / \"launches.json\"\n_TEMPLATES = _STATE_DIR / \"templates.json\"\n\n_wallet_sol: str = \"\"\n_wallet_bsc: str = \"\"\n_wallet_ready: bool = False\n_dashboard_started: bool = False\n_launches_lock = Lock()\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Wallet Preflight\n# ══════════════════════════════════════════════════════════════════════\n\ndef _run_onchainos(*args: str) -> dict:\n \"\"\"Run onchainos CLI and return parsed JSON output.\"\"\"\n cmd = [onchainos_bin()]\n cmd.extend(args)\n try:\n proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)\n if proc.returncode != 0:\n return {\"ok\": False, \"error\": proc.stderr.strip() or f\"exit code {proc.returncode}\"}\n return json.loads(proc.stdout)\n except json.JSONDecodeError:\n return {\"ok\": False, \"error\": f\"Invalid JSON: {proc.stdout[:200]}\"}\n except Exception as e:\n return {\"ok\": False, \"error\": str(e)}\n\n\ndef wallet_preflight() -> bool:\n \"\"\"Check wallet login and resolve addresses.\"\"\"\n global _wallet_sol, _wallet_bsc, _wallet_ready\n\n # Check login\n status = _run_onchainos(\"wallet\", \"status\")\n if not status.get(\"data\", {}).get(\"loggedIn\"):\n print(\"ERROR: Wallet not logged in.\")\n print(\" Run: onchainos wallet login \u003cyour-email>\")\n return False\n\n email = status[\"data\"].get(\"email\", \"unknown\")\n print(f\" Wallet logged in: {email}\")\n\n # Resolve addresses from wallet addresses command\n addrs = _run_onchainos(\"wallet\", \"addresses\")\n addr_data = addrs.get(\"data\", {})\n if isinstance(addr_data, dict):\n sol_list = addr_data.get(\"solana\", [])\n if sol_list:\n _wallet_sol = sol_list[0].get(\"address\", \"\")\n # EVM address for BSC/ETH — find chainIndex 56 (BSC) or use first EVM\n evm_list = addr_data.get(\"evm\", [])\n for evm in evm_list:\n if evm.get(\"chainIndex\") == \"56\":\n _wallet_bsc = evm.get(\"address\", \"\")\n break\n if not _wallet_bsc and evm_list:\n _wallet_bsc = evm_list[0].get(\"address\", \"\")\n\n # Use overrides if set\n _wallet_sol = C.WALLET_SOL or _wallet_sol\n _wallet_bsc = C.WALLET_BSC or _wallet_bsc\n\n if _wallet_sol:\n print(f\" SOL wallet: {_wallet_sol[:6]}...{_wallet_sol[-4:]}\")\n if _wallet_bsc:\n print(f\" BSC wallet: {_wallet_bsc[:6]}...{_wallet_bsc[-4:]}\")\n\n if _wallet_sol or _wallet_bsc:\n _wallet_ready = True\n return True\n return False\n\n\ndef get_wallet(chain: str) -> str:\n \"\"\"Get the resolved wallet address for a chain.\"\"\"\n if chain == \"solana\":\n return _wallet_sol\n elif chain == \"bsc\":\n return _wallet_bsc\n return \"\"\n\n\ndef get_balance(chain: str) -> float:\n \"\"\"Get native token balance for a chain.\n\n onchainos wallet balance returns:\n {\n \"data\": {\n \"solAddress\": \"...\",\n \"evmAddress\": \"...\",\n \"details\": [\n { \"tokenAssets\": [ { \"symbol\": \"SOL\", \"balance\": \"0.58\", \"chainIndex\": \"501\", ... } ] }\n ]\n }\n }\n \"\"\"\n # Use chain ID filter if possible, fallback to no filter\n chain_id = C.SOL_CHAIN_INDEX if chain == \"solana\" else C.BSC_CHAIN_INDEX if chain == \"bsc\" else \"\"\n native_sym = \"SOL\" if chain == \"solana\" else \"BNB\" if chain == \"bsc\" else \"\"\n\n # onchainos CLI can return 0.0 on first call (flaky), retry once\n for _attempt in range(2):\n result = _run_onchainos(\"wallet\", \"balance\")\n data = result.get(\"data\", {})\n if isinstance(data, dict):\n for detail in data.get(\"details\", []):\n for asset in detail.get(\"tokenAssets\", []):\n sym = asset.get(\"symbol\", \"\").upper()\n asset_chain = asset.get(\"chainIndex\", \"\")\n if sym == native_sym and (not chain_id or asset_chain == chain_id):\n bal = float(asset.get(\"balance\", 0))\n if bal > 0:\n return bal\n return 0.0\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Quick Launch — one call does everything\n# ══════════════════════════════════════════════════════════════════════\n\ndef _ensure_wallet():\n \"\"\"Lazy wallet preflight — runs once, caches result.\"\"\"\n global _wallet_ready\n if _wallet_ready:\n return\n if not wallet_preflight():\n raise RuntimeError(\n \"Wallet not logged in. Run: onchainos wallet login \u003cyour-email>\"\n )\n _wallet_ready = True\n\n\ndef _normalize_image(image: str) -> str:\n \"\"\"Accept file path, URL, or base64/data-URI. Returns a path or URL\n that ipfs.py can consume directly.\n\n Supported inputs:\n - \"/path/to/image.png\" → returned as-is\n - \"https://example.com/dog.png\" → returned as-is (ipfs handles download)\n - \"data:image/png;base64,iVBOR…\" → decoded, saved to temp file\n - raw base64 string (>500 chars) → decoded, saved to temp file\n \"\"\"\n if not image:\n raise ValueError(\"Image is required. Provide a file path, URL, or base64 string.\")\n\n # URL — pass through\n if image.startswith(\"http://\") or image.startswith(\"https://\"):\n return image\n\n # File path — pass through if it exists\n if os.path.exists(image):\n return image\n\n # Data URI: data:image/png;base64,xxxxx\n if image.startswith(\"data:\"):\n try:\n header, b64data = image.split(\",\", 1)\n # Extract extension from MIME: data:image/png;base64 → png\n mime = header.split(\":\")[1].split(\";\")[0] # image/png\n ext = mime.split(\"/\")[1] if \"/\" in mime else \"png\"\n except (ValueError, IndexError):\n ext = \"png\"\n b64data = image.split(\",\")[-1]\n data = b64.b64decode(b64data)\n tmp = tempfile.NamedTemporaryFile(suffix=f\".{ext}\", delete=False, dir=\"/tmp\")\n tmp.write(data)\n tmp.close()\n return tmp.name\n\n # Raw base64 (long string, not a file path)\n if len(image) > 500:\n try:\n data = b64.b64decode(image)\n tmp = tempfile.NamedTemporaryFile(suffix=\".png\", delete=False, dir=\"/tmp\")\n tmp.write(data)\n tmp.close()\n return tmp.name\n except Exception:\n pass # Not valid base64 — fall through\n\n # Assume file path — let downstream handle the error\n return image\n\n\nasync def quick_launch(\n name: str,\n symbol: str,\n description: str = \"\",\n image: str = \"\",\n launchpad: str = \"pumpfun\",\n buy_amount: float = 0.0,\n website: str = \"\",\n twitter: str = \"\",\n telegram: str = \"\",\n slippage_bps: int = 1000,\n **extras,\n) -> LaunchResult:\n \"\"\"One-call token launch. Handles wallet, image, IPFS, signing, broadcast.\n\n Args:\n name: Token name (e.g. \"MoonDog\")\n symbol: Token ticker (e.g. \"MDOG\")\n description: Token description\n image: File path, URL, or base64/data-URI\n launchpad: \"pumpfun\" | \"bags\" | \"letsbonk\" | \"moonit\" | \"fourmeme\" | \"flap\"\n buy_amount: Bundled buy in native token (0 = create only)\n website: Optional website URL\n twitter: Optional Twitter URL\n telegram: Optional Telegram URL\n slippage_bps: Slippage tolerance in basis points (default 1000 = 10%)\n **extras: Launchpad-specific options (priority_fee, pool, buy_tax, etc.)\n\n Returns:\n LaunchResult with success, token_address, tx_hash, explorer_url, etc.\n\n Example:\n result = await quick_launch(\"MoonDog\", \"MDOG\", \"a good boy\", \"/tmp/dog.png\")\n result = await quick_launch(\"MoonDog\", \"MDOG\", \"a good boy\", \"https://i.imgur.com/dog.png\",\n buy_amount=0.1, twitter=\"https://twitter.com/moondog\")\n \"\"\"\n # ── 0. Input validation ───────────────────────────────────────────\n name = name.strip()\n symbol = symbol.strip().upper()\n if not name or len(name) > 32:\n return LaunchResult(success=False, error=\"Token name must be 1–32 characters.\")\n if not symbol or len(symbol) > 10 or not symbol.replace(\"-\", \"\").replace(\"_\", \"\").isalnum():\n return LaunchResult(success=False, error=\"Symbol must be 1–10 alphanumeric characters.\")\n if launchpad not in C.LAUNCHPAD_CHAIN:\n return LaunchResult(success=False, error=f\"Unknown launchpad '{launchpad}'. Choose from: {list(C.LAUNCHPAD_CHAIN)}\")\n if buy_amount \u003c 0:\n return LaunchResult(success=False, error=\"buy_amount cannot be negative.\")\n if not 0 \u003c= slippage_bps \u003c= 10000:\n return LaunchResult(success=False, error=\"slippage_bps must be 0–10000 (0–100%).\")\n\n # ── 1. Auto-start dashboard ────────────────────────────────────────\n global _dashboard_started\n if not _dashboard_started:\n _start_dashboard()\n _dashboard_started = True\n\n # ── 2. Wallet (lazy init) ──────────────────────────────────────────\n _ensure_wallet()\n\n adapter = get_adapter(launchpad)\n chain = adapter.chain\n wallet = get_wallet(chain)\n native = \"SOL\" if chain == \"solana\" else \"BNB\"\n\n # ── 3. Wallet + balance checks (skipped in DRY_RUN) ──────────────\n balance = 0.0\n if C.DRY_RUN:\n wallet = wallet or f\"DRY_RUN_{chain.upper()}_WALLET\"\n else:\n if not wallet:\n return LaunchResult(\n success=False,\n error=f\"No {chain} wallet found. Check onchainos wallet addresses.\",\n )\n\n balance = get_balance(chain)\n buffer = C.MIN_BALANCE_BUFFER.get(chain, 0.02)\n needed = buy_amount + buffer\n\n if balance \u003c needed:\n return LaunchResult(\n success=False,\n error=f\"Insufficient balance: {balance:.4f} {native} \u003c {needed:.4f} {native} needed\",\n )\n\n # ── 4. Normalize image ─────────────────────────────────────────────\n try:\n image_path = _normalize_image(image) if image else \"\"\n except Exception as e:\n if not C.DRY_RUN:\n return LaunchResult(success=False, error=f\"Image error: {e}\")\n image_path = \"\"\n\n # ── 5. Build params ────────────────────────────────────────────────\n params = LaunchParams(\n name=name,\n symbol=symbol,\n description=description,\n image_path=image_path,\n website=website,\n twitter=twitter,\n telegram=telegram,\n launchpad=launchpad,\n buy_amount=buy_amount,\n slippage_bps=slippage_bps,\n mev_protection=C.MEV_PROTECTION,\n wallet_address=wallet,\n extras=dict(extras) if extras else {},\n )\n\n # ── 6. Confirmation display ────────────────────────────────────────\n est_cost = adapter.estimate_cost(params)\n mode = \"DRY RUN\" if C.DRY_RUN else \"LIVE\"\n lp_name = C.LAUNCHPAD_DISPLAY.get(launchpad, launchpad)\n\n print(f\"\\n{'─' * 54}\")\n print(f\" Token Launch — {mode}\")\n print(f\"{'─' * 54}\")\n print(f\" Name: {name} ({symbol})\")\n print(f\" Desc: {description[:60]}{'…' if len(description) > 60 else ''}\")\n print(f\" Launchpad: {lp_name} ({chain})\")\n print(f\" Wallet: {wallet[:8]}…{wallet[-4:]}\")\n if not C.DRY_RUN:\n print(f\" Balance: {balance:.4f} {native}\")\n print(f\" Buy: {buy_amount} {native}\" if buy_amount > 0 else \" Buy: create only\")\n print(f\" Est. cost: ~{est_cost:.4f} {native}\")\n if website: print(f\" Website: {website}\")\n if twitter: print(f\" Twitter: {twitter}\")\n if telegram: print(f\" Telegram: {telegram}\")\n print(f\"{'─' * 54}\\n\")\n\n # ── 7. Confirmation gate (always enforced in LIVE mode) ────────────\n # Live mode always requires explicit user confirmation to prevent\n # accidental on-chain TX. DRY_RUN bypasses this gate.\n if C.CONFIRM_REQUIRED and not C.DRY_RUN:\n print(\" ⚠ LIVE MODE — token creation is IRREVERSIBLE.\")\n print(' Type \"confirm\" to proceed, anything else to abort.')\n try:\n answer = input(\" > \").strip().lower()\n except EOFError:\n answer = \"\"\n if answer != \"confirm\":\n return LaunchResult(success=False, error=\"Launch cancelled by user.\")\n\n # ── 8. Execute (pass cached balance to avoid double network call) ─\n return await execute_launch(params, _balance=balance)\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Launch Execution (internal pipeline)\n# ══════════════════════════════════════════════════════════════════════\n\nasync def execute_launch(params: LaunchParams, _balance: float = -1) -> LaunchResult:\n \"\"\"Full launch pipeline: IPFS upload → adapter launch → save record.\n\n Args:\n params: Launch parameters (wallet_address may already be set by quick_launch)\n _balance: Pre-fetched balance from quick_launch (avoids double network call).\n Pass -1 to fetch fresh.\n \"\"\"\n adapter = get_adapter(params.launchpad)\n chain = adapter.chain\n\n # ── DRY_RUN short-circuit: skip balance check and IPFS upload ─────\n if C.DRY_RUN:\n print(f\"\\n [DRY RUN] Simulating {params.name} ({params.symbol}) on {adapter.display_name}...\")\n result = await adapter.launch(params)\n _save_launch_record(params, result)\n return result\n\n # ── Resolve wallet (skip if already set by quick_launch) ──────────\n if not params.wallet_address:\n params.wallet_address = get_wallet(chain)\n if not params.wallet_address:\n return LaunchResult(success=False, error=f\"No wallet found for {chain}\")\n\n # ── Check balance (skip if pre-verified by quick_launch) ──────────\n balance = _balance if _balance >= 0 else get_balance(chain)\n estimated_cost = adapter.estimate_cost(params)\n buffer = C.MIN_BALANCE_BUFFER.get(chain, 0.02)\n\n if balance \u003c params.buy_amount + buffer:\n return LaunchResult(\n success=False,\n error=f\"Insufficient balance: {balance:.4f} \u003c {params.buy_amount + buffer:.4f} needed\",\n )\n\n native = \"SOL\" if chain == \"solana\" else \"BNB\"\n print(f\"\\n Launching {params.name} ({params.symbol}) on {adapter.display_name}...\")\n print(f\" Wallet: {params.wallet_address[:8]}…{params.wallet_address[-4:]} | {balance:.4f} {native}\")\n\n # ── Upload image + metadata to IPFS ─────────────────────────────────\n print(\" Step 1/3: Uploading image + metadata to IPFS...\")\n try:\n ipfs_result = ipfs.upload_all(\n image_path=params.image_path,\n name=params.name,\n symbol=params.symbol,\n description=params.description,\n website=params.website,\n twitter=params.twitter,\n telegram=params.telegram,\n )\n params.image_cid = ipfs_result[\"image_cid\"]\n params.metadata_cid = ipfs_result[\"metadata_cid\"]\n params.metadata_uri = ipfs_result[\"metadata_uri\"]\n except Exception as e:\n return LaunchResult(success=False, error=f\"IPFS upload failed: {e}\")\n\n # ── Execute launch ────────────────────────────────────────────────\n print(\" Step 2/3: Executing launch...\")\n result = await adapter.launch(params)\n\n # ── Save record ───────────────────────────────────────────────────\n print(\" Step 3/3: Saving launch record...\")\n _save_launch_record(params, result)\n\n # ── Lark notification (fire-and-forget) ──────────────────────────\n webhook = C.LARK_WEBHOOK or os.environ.get(\"LARK_WEBHOOK\", \"\")\n if webhook and result.success:\n asyncio.create_task(_send_lark_notification(webhook, params, result))\n\n # ── Post-launch monitor (background thread — non-blocking) ──────\n if result.success and result.token_address and not C.DRY_RUN:\n def _bg_monitor():\n try:\n import post_launch\n post_launch.snapshot(\n result.token_address,\n chain=C.SOL_CHAIN_INDEX if chain == \"solana\" else C.BSC_CHAIN_INDEX,\n launchpad=params.launchpad,\n wallet=params.wallet_address,\n )\n print(f\"\\n Run live monitor:\")\n print(f\" python3 post_launch.py {result.token_address} --refresh 10\")\n except Exception as e:\n print(f\" [Monitor] Failed to show stats: {e}\")\n\n Thread(target=_bg_monitor, daemon=True).start()\n print(\" Post-launch stats loading in background...\")\n\n return result\n\n\ndef _save_launch_record(params: LaunchParams, result: LaunchResult):\n \"\"\"Append launch record to state/launches.json (thread-safe, atomic).\"\"\"\n _STATE_DIR.mkdir(exist_ok=True)\n\n record = {\n \"timestamp\": datetime.now(timezone.utc).isoformat(),\n \"launchpad\": params.launchpad,\n \"chain\": C.LAUNCHPAD_CHAIN.get(params.launchpad, \"unknown\"),\n \"name\": params.name,\n \"symbol\": params.symbol,\n \"description\": params.description,\n \"image_cid\": params.image_cid,\n \"metadata_cid\": params.metadata_cid,\n \"metadata_uri\": params.metadata_uri,\n \"website\": params.website,\n \"twitter\": params.twitter,\n \"telegram\": params.telegram,\n \"buy_amount\": params.buy_amount,\n \"wallet\": params.wallet_address,\n \"token_address\": result.token_address,\n \"tx_hash\": result.tx_hash,\n \"explorer_url\": result.explorer_url,\n \"trade_page_url\": result.trade_page_url,\n \"success\": result.success,\n \"error\": result.error,\n }\n\n with _launches_lock:\n records = []\n if _LAUNCHES.exists():\n try:\n records = json.loads(_LAUNCHES.read_text())\n except json.JSONDecodeError:\n records = []\n records.append(record)\n\n # Atomic write: write to temp file, then rename\n tmp_path = _LAUNCHES.with_suffix(\".json.tmp\")\n tmp_path.write_text(json.dumps(records, indent=2, ensure_ascii=False))\n tmp_path.rename(_LAUNCHES)\n\n print(f\" [State] Launch record saved ({len(records)} total)\")\n\n\nasync def _send_lark_notification(webhook: str, params: LaunchParams, result: LaunchResult):\n \"\"\"Send launch notification to Lark webhook (async-safe).\"\"\"\n try:\n import httpx\n msg = {\n \"msg_type\": \"text\",\n \"content\": {\n \"text\": (\n f\"Token Launched!\\n\"\n f\"Name: {params.name} ({params.symbol})\\n\"\n f\"Launchpad: {C.LAUNCHPAD_DISPLAY.get(params.launchpad, params.launchpad)}\\n\"\n f\"Token: {result.token_address}\\n\"\n f\"TX: {result.explorer_url}\\n\"\n f\"Trade: {result.trade_page_url}\\n\"\n f\"Buy: {params.buy_amount} {'SOL' if 'sol' in params.launchpad else 'BNB'}\"\n )\n }\n }\n async with httpx.AsyncClient() as client:\n await client.post(webhook, json=msg, timeout=10)\n except Exception as e:\n print(f\" [Lark] Notification failed: {e}\")\n\n\n# ══════════════════════════════════════════════════════════════════════\n# Dashboard\n# ══════════════════════════════════════════════════════════════════════\n\ndef _start_dashboard():\n \"\"\"Start the web dashboard on DASHBOARD_PORT.\"\"\"\n dashboard_html = _BASE_DIR / \"dashboard.html\"\n if not dashboard_html.exists():\n print(f\" Dashboard HTML not found, skipping\")\n return\n\n class Handler(SimpleHTTPRequestHandler):\n def __init__(self, *args, **kwargs):\n super().__init__(*args, directory=str(_BASE_DIR), **kwargs)\n\n def do_GET(self):\n if self.path == \"/\" or self.path == \"/index.html\":\n self.path = \"/dashboard.html\"\n elif self.path.endswith((\".py\", \".json\", \".yaml\", \".txt\", \".md\", \".gitignore\")):\n # Block access to source code and config files\n self.send_error(403, \"Forbidden\")\n return\n elif self.path == \"/api/launches\":\n self._json_response(_load_launches())\n return\n elif self.path == \"/api/wallet\":\n self._json_response({\n \"sol\": {\"address\": _wallet_sol, \"balance\": get_balance(\"solana\") if _wallet_sol else 0},\n \"bsc\": {\"address\": _wallet_bsc, \"balance\": get_balance(\"bsc\") if _wallet_bsc else 0},\n \"mode\": \"DRY RUN\" if C.DRY_RUN else \"LIVE\",\n })\n return\n elif self.path.startswith(\"/api/token-stats?\"):\n self._handle_token_stats()\n return\n return super().do_GET()\n\n def _handle_token_stats(self):\n \"\"\"Fetch live bonding curve + price data for a token.\"\"\"\n from urllib.parse import urlparse, parse_qs\n qs = parse_qs(urlparse(self.path).query)\n addr = qs.get(\"address\", [\"\"])[0]\n chain = qs.get(\"chain\", [\"501\"])[0]\n if not addr or addr.startswith(\"DRY_RUN\"):\n self._json_response({\"bonding_pct\": None, \"price_usd\": None, \"mcap_usd\": None, \"holders\": None})\n return\n try:\n result = _run_onchainos(\"memepump\", \"token-details\", \"--chain\", chain, \"--address\", addr)\n data = result.get(\"data\", {})\n bp = data.get(\"bondingPercent\", \"\")\n mkt = data.get(\"market\", {})\n tags = data.get(\"tags\", {})\n self._json_response({\n \"bonding_pct\": float(bp) if bp else None,\n \"price_usd\": float(mkt.get(\"priceUsd\", \"0\") or \"0\") or None,\n \"mcap_usd\": float(mkt.get(\"marketCapUsd\", \"0\") or \"0\") or None,\n \"holders\": int(tags.get(\"totalHolders\", \"0\") or \"0\"),\n \"volume_1h\": float(mkt.get(\"volumeUsd1h\", \"0\") or \"0\") or None,\n \"buy_count\": int(mkt.get(\"buyTxCount1h\", \"0\") or \"0\"),\n \"sell_count\": int(mkt.get(\"sellTxCount1h\", \"0\") or \"0\"),\n })\n except Exception:\n self._json_response({\"bonding_pct\": None, \"price_usd\": None, \"mcap_usd\": None, \"holders\": None})\n\n def _json_response(self, data):\n body = json.dumps(data, ensure_ascii=False).encode()\n self.send_response(200)\n self.send_header(\"Content-Type\", \"application/json\")\n self.send_header(\"Content-Length\", str(len(body)))\n self.end_headers()\n self.wfile.write(body)\n\n def log_message(self, fmt, *args):\n pass # Suppress request logs\n\n try:\n server = HTTPServer((\"127.0.0.1\", C.DASHBOARD_PORT), Handler)\n t = Thread(target=server.serve_forever, daemon=True)\n t.start()\n print(f\" Dashboard → http://localhost:{C.DASHBOARD_PORT}\")\n except OSError as e:\n print(f\" Dashboard failed to start: {e}\")\n\n\ndef _load_launches() -> list:\n \"\"\"Load launch history from disk.\"\"\"\n if _LAUNCHES.exists():\n try:\n return json.loads(_LAUNCHES.read_text())\n except json.JSONDecodeError:\n return []\n return []\n\n\n# ══════════════════════════════════════════════════════════════════════\n# CLI Entry Point\n# ══════════════════════════════════════════════════════════════════════\n\ndef print_banner():\n mode = \"DRY RUN\" if C.DRY_RUN else \"LIVE\"\n print(\"=\" * 60)\n print(f\" 一键发币 Token Launch v1.0 [{mode}]\")\n print(f\" Launchpads: pump.fun | Bags | LetsBonk | Moonit | Four.Meme | Flap\")\n print(f\" Dashboard: http://localhost:{C.DASHBOARD_PORT}\")\n print(\"=\" * 60)\n\n\ndef main():\n global _dashboard_started\n print_banner()\n\n # Wallet preflight\n if not wallet_preflight():\n sys.exit(1)\n\n # Start dashboard\n _start_dashboard()\n _dashboard_started = True\n\n print(\"\\n Ready. One-call launch:\")\n print(\" from token_launch import quick_launch\")\n print(' await quick_launch(\"Name\", \"SYM\", \"desc\", \"/path/to/image.png\")')\n print(f\"\\n Dashboard: http://localhost:{C.DASHBOARD_PORT}\")\n print(\" Press Ctrl+C to stop.\\n\")\n\n try:\n while True:\n time.sleep(1)\n except KeyboardInterrupt:\n print(\"\\n Shutting down.\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":28215,"content_sha256":"2a8704dd35b8b4bc4ca65a7290cbc940afd8bdb2a76eed35cebc2229bbe775a6"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"一键发币 v1.0 — Multi-Launchpad Token Launch","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"One-click token creation with optional bundled initial buy across 6+ launchpads.","type":"text"}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Live Trading Confirmation Protocol","type":"text"}]},{"type":"paragraph","content":[{"text":"These gates are ","type":"text"},{"text":"mandatory","type":"text","marks":[{"type":"strong"}]},{"text":" for the AI agent driving this skill. Before any call that signs or broadcasts an on-chain transaction (any ","type":"text"},{"text":"onchainos swap swap","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"onchainos wallet contract-call","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"onchainos dex swap","type":"text","marks":[{"type":"code_inline"}]},{"text":", or any internal write code path that ends in a real on-chain submission), ALL of the following must be true:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Paper / preview mode is the default.","type":"text","marks":[{"type":"strong"}]},{"text":" Real on-chain writes MUST NOT be broadcast unless the user has explicitly switched to live mode via the confirmation flow in rule 2. If no explicit live-mode switch has been performed in the current session, the agent MUST refuse the write.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Live-mode switch requires a typed user confirmation.","type":"text","marks":[{"type":"strong"}]},{"text":" Before flipping to live mode, the agent MUST display to the user: wallet address (","type":"text"},{"text":"onchainos wallet addresses","type":"text","marks":[{"type":"code_inline"}]},{"text":"), current balance (","type":"text"},{"text":"onchainos wallet balance","type":"text","marks":[{"type":"code_inline"}]},{"text":"), the configured per-trade / per-session risk limits from this skill's config, and a statement that on-chain writes are irreversible. The user MUST then reply with an unambiguous typed confirmation (e.g. ","type":"text"},{"text":"confirm live mode","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"确认开启实盘","type":"text","marks":[{"type":"code_inline"}]},{"text":"). A conversational \"yes / sure / 可以\" alone does not satisfy this gate.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Preview before every write.","type":"text","marks":[{"type":"strong"}]},{"text":" Every write operation MUST first generate a preview (e.g. ","type":"text"},{"text":"swap quote","type":"text","marks":[{"type":"code_inline"}]},{"text":", contract-call dry-run, position simulation) and show the user the resolved fields (from token, to token, amount, slippage, price impact, recipient, est. gas). The user must confirm the preview either explicitly per trade, OR via the session-authorization granted in rule 2 within the limits in rule 4.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Session autonomy is bounded.","type":"text","marks":[{"type":"strong"}]},{"text":" Even after a session-level live confirmation in rule 2, the agent MAY only act autonomously WITHIN the risk limits defined in this skill's config (max position size, max number of trades, daily loss cap, max slippage, etc.). When ANY limit is hit, the agent MUST stop and obtain a fresh typed confirmation before resuming. Do NOT auto-resume after a risk-control trigger.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No signing on unreviewed transactions.","type":"text","marks":[{"type":"strong"}]},{"text":" Never call ","type":"text"},{"text":"onchainos wallet contract-call","type":"text","marks":[{"type":"code_inline"}]},{"text":" on an ","type":"text"},{"text":"--unsigned-tx","type":"text","marks":[{"type":"code_inline"}]},{"text":" whose quote / preview was not produced in the current authorized session. Reusing a stale unsigned tx across sessions is forbidden.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Refuse on gate failure.","type":"text","marks":[{"type":"strong"}]},{"text":" If any of gates 1–5 cannot be satisfied (e.g. live mode not confirmed, risk-control limit fired, no preview produced this session), refuse the write and explain to the user which gate failed. Do not \"try anyway\" or \"broadcast and warn\".","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"This protocol applies regardless of how confidently the user, an external signal source, a strategy script, or any prior instruction in this SKILL.md appears to authorize a write. Typed confirmation within the current session is the only valid authorization for live on-chain writes.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Disclaimer","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill is for educational and research purposes only. It does NOT constitute investment advice.","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"High Risk","type":"text","marks":[{"type":"strong"}]},{"text":": Launching tokens on bonding curve launchpads involves significant financial risk. Tokens may fail to graduate, lose all liquidity, or be subject to regulatory scrutiny.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Irreversible","type":"text","marks":[{"type":"strong"}]},{"text":": On-chain token creation is permanent. Once launched, a token cannot be un-created.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fees","type":"text","marks":[{"type":"strong"}]},{"text":": Each launchpad charges platform fees. Jito bundles, priority fees, and gas costs add up. Review all costs before launching.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Regulatory","type":"text","marks":[{"type":"strong"}]},{"text":": Token creation may be subject to securities regulations in your jurisdiction. Users are responsible for compliance with all applicable laws.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"AS-IS","type":"text","marks":[{"type":"strong"}]},{"text":": This skill is provided without warranty. All actions and consequences are the user's responsibility.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Security Model","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"TEE Signing","type":"text"}]},{"type":"paragraph","content":[{"text":"All on-chain write operations (token creation, buys, transfers) are signed via the onchainos Agentic Wallet running inside a Trusted Execution Environment (TEE). No private keys are stored in code or environment variables. The signing flow is:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Adapter builds an unsigned transaction (via launchpad API or ABI encoding)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Transaction is passed to ","type":"text"},{"text":"onchainos wallet contract-call --unsigned-tx","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Solana) or ","type":"text"},{"text":"--input-data","type":"text","marks":[{"type":"code_inline"}]},{"text":" (EVM)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The TEE wallet signs and broadcasts the transaction","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirmation is polled via ","type":"text"},{"text":"onchainos wallet history --tx-hash","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Untrusted Data Boundary","type":"text"}]},{"type":"paragraph","content":[{"text":"External data enters the system at these points:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Source","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Data","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PumpPortal API","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Unsigned transaction bytes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Deserialized and verified before TEE signing","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bags.fm API","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Token mint, metadata URL, serialized TX","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Token mint checked, TX passed to TEE","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Moonit API","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Serialized TX, token mint","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TX passed to TEE for signing","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User input","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Token name, symbol, description, image","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Length limits enforced, image format validated","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"IPFS upload","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CID hash","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Immutable content-addressed -- no validation needed","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pinata API","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Upload response (CID)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CID format validated","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"User-supplied strings (name, symbol, description) are passed to launchpad APIs and on-chain metadata. They are NOT used in shell commands or SQL queries. Image files are validated for format and size before IPFS upload.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Confirmation Gate","type":"text"}]},{"type":"paragraph","content":[{"text":"Live mode (","type":"text"},{"text":"DRY_RUN=False","type":"text","marks":[{"type":"code_inline"}]},{"text":") always requires explicit user confirmation (typing \"confirm\") before any on-chain transaction. The ","type":"text"},{"text":"auto_confirm","type":"text","marks":[{"type":"code_inline"}]},{"text":" parameter only applies in DRY_RUN mode. This prevents accidental irreversible token creation.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"File Structure","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Token Launch/\n├── SKILL.md ← This file (strategy spec)\n├── config.py ← All configurable parameters\n├── token_launch.py ← Main program\n├── launchpads/ ← Per-launchpad adapters\n│ ├── __init__.py\n│ ├── base.py ← Abstract base class\n│ ├── pumpfun.py ← pump.fun via PumpPortal API\n│ ├── bags.py ← Bags.fm via REST SDK\n│ ├── letsbonk.py ← LetsBonk via API\n│ ├── moonit.py ← Moonit via SDK\n│ ├── fourmeme.py ← Four.Meme (BSC) via contract\n│ └── flap.py ← Flap.sh (BSC) via contract\n├── ipfs.py ← IPFS upload (pump.fun free endpoint + Pinata fallback)\n├── post_launch.py ← Post-launch monitor\n├── dashboard.html ← Web Dashboard UI\n├── requirements.txt ← Python dependencies\n└── state/ ← [Auto-generated]\n └── launches.json ← Launch history","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Install onchainos CLI (>= 2.1.0)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"onchainos --version\n# If not installed, follow onchainos official docs","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Login to Agentic Wallet (TEE Signing)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"onchainos wallet login \u003cyour-email>\nonchainos wallet status\n# → loggedIn: true\n\n# Confirm Solana address\nonchainos wallet balance --chain solana\n# → address: 2HNq...ErwW\n\n# Confirm BSC address (if using BSC launchpads)\nonchainos wallet balance --chain bsc","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. IPFS Upload (No Setup Needed)","type":"text"}]},{"type":"paragraph","content":[{"text":"IPFS upload uses pump.fun's free ","type":"text"},{"text":"/api/ipfs","type":"text","marks":[{"type":"code_inline"}]},{"text":" endpoint by default — ","type":"text"},{"text":"no API key required","type":"text","marks":[{"type":"strong"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Optional fallback: Pinata (set ","type":"text"},{"text":"export PINATA_JWT=\"your_jwt\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" if you want redundancy).","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Python Dependencies","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"pip install -r requirements.txt\n# or manually:\npip install httpx base58 solders","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Supported Launchpads","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Solana","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Launchpad","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Protocol","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Migration Target","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bundled Buy","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MEV Protection","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API Type","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pump.fun","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pump.fun bonding curve","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Raydium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes (Jito bundle)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Jito bundle","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PumpPortal REST","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bags.fm","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Meteora DBC","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Meteora","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes (atomic)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Built-in","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Official REST SDK","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LetsBonk","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bonk bonding curve","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Raydium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Built-in","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MCP / REST","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Moonit","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Moonit bonding curve","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Raydium/Meteora","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Built-in","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Official SDK","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"BSC","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Launchpad","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Protocol","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Migration Target","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bundled Buy","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tax Token","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API Type","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Four.Meme","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Four.Meme bonding curve","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PancakeSwap","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Contract call","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Flap.sh","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Flap bonding curve","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PancakeSwap V2/V3","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes (buy/sell tax)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Contract call","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"User Flow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Overview","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"┌──────────────────────────────────────────────────────────────────────┐\n│ USER: \"发币\" / \"launch token\" / \"create a meme coin\" │\n└────────────────────────────┬─────────────────────────────────────────┘\n │\n ▼\n┌──────────────────────────────────────────────────────────────────────┐\n│ STEP 1: BASIC INFO │\n│ │\n│ ┌──────────────┬───────────────────────────────────────────────┐ │\n│ │ Token Name │ \"DogWifHat\" [Required] │ │\n│ │ Ticker │ \"WIF\" [Required] │ │\n│ │ Description │ \"The dog with the hat\" [Required] │ │\n│ │ Image │ ./wif.png or URL [Required] │ │\n│ └──────────────┴───────────────────────────────────────────────┘ │\n└────────────────────────────┬─────────────────────────────────────────┘\n │\n ▼\n┌──────────────────────────────────────────────────────────────────────┐\n│ STEP 2: SOCIAL LINKS (Optional) │\n│ │\n│ ┌──────────────┬───────────────────────────────────────────────┐ │\n│ │ Website │ \u003cyour-website-url> [Optional] │ │\n│ │ Twitter / X │ \u003cyour-twitter-url> [Optional] │ │\n│ │ Telegram │ \u003cyour-telegram-url> [Optional] │ │\n│ └──────────────┴───────────────────────────────────────────────┘ │\n└────────────────────────────┬─────────────────────────────────────────┘\n │\n ▼\n┌──────────────────────────────────────────────────────────────────────┐\n│ STEP 3: CHOOSE LAUNCHPAD │\n│ │\n│ Solana: │\n│ ┌─────┬──────────────────────────────────────────────────────────┐ │\n│ │ 1 │ 🟢 pump.fun — Largest SOL launchpad, Raydium migrate │ │\n│ │ 2 │ 🔵 Bags.fm — Fee sharing, Meteora DBC │ │\n│ │ 3 │ 🟡 LetsBonk — BONK ecosystem, Raydium migrate │ │\n│ │ 4 │ 🟠 Moonit — Creator rewards, 80% fee share │ │\n│ └─────┴──────────────────────────────────────────────────────────┘ │\n│ │\n│ BSC: │\n│ ┌─────┬──────────────────────────────────────────────────────────┐ │\n│ │ 5 │ 🔴 Four.Meme — Largest BSC launchpad, PCS migrate │ │\n│ │ 6 │ 🟣 Flap.sh — Tax tokens, vanity addr, PCS V3 │ │\n│ └─────┴──────────────────────────────────────────────────────────┘ │\n│ │\n│ Default: pump.fun (if user doesn't specify) │\n└────────────────────────────┬─────────────────────────────────────────┘\n │\n ▼\n┌──────────────────────────────────────────────────────────────────────┐\n│ STEP 4: LAUNCHPAD-SPECIFIC CONFIG │\n│ │\n│ ┌─── pump.fun ────────────────────────────────────────────────────┐ │\n│ │ Category: (not applicable — pump.fun has no categories) │ │\n│ │ Priority Fee: 0.0005 SOL (default) │ │\n│ │ Tip Fee: 0.0001 SOL (default) │ │\n│ └─────────────────────────────────────────────────────────────────┘ │\n│ │\n│ ┌─── Bags.fm ─────────────────────────────────────────────────────┐ │\n│ │ Fee Sharing: Creator 100% (default) or split with others │ │\n│ │ Fee Claimers: [{address, bps}] — must total 10,000 bps │ │\n│ └─────────────────────────────────────────────────────────────────┘ │\n│ │\n│ ┌─── Four.Meme ───────────────────────────────────────────────────┐ │\n│ │ Category: Meme/AI/DeFi/Games/Infra/De-Sci/Social/... │ │\n│ │ Gas Price: auto (default) or custom wei │ │\n│ └─────────────────────────────────────────────────────────────────┘ │\n│ │\n│ ┌─── Flap.sh ─────────────────────────────────────────────────────┐ │\n│ │ Category: (via extensionData) │ │\n│ │ Buy Tax: 0-10000 bps │ │\n│ │ Sell Tax: 0-10000 bps │ │\n│ │ Tax Duration: seconds │ │\n│ │ Tax Split: mktBps + deflationBps + dividendBps + lpBps │ │\n│ │ DEX Target: PancakeSwap V2 or V3 │ │\n│ │ LP Fee Tier: (if V3) │ │\n│ │ Vanity Salt: bytes32 (optional, for custom token address) │ │\n│ └─────────────────────────────────────────────────────────────────┘ │\n└────────────────────────────┬─────────────────────────────────────────┘\n │\n ▼\n┌──────────────────────────────────────────────────────────────────────┐\n│ STEP 5: BUNDLED INITIAL BUY (捆绑买入) │\n│ │\n│ \"Buy your own token at launch?\" │\n│ │\n│ ┌──────────────────┬────────────────────────────────────────────┐ │\n│ │ Buy Amount │ 0 = create only │ │\n│ │ │ 0.5 SOL = buy at launch (bundled) │ │\n│ │ MEV Protection │ ON (Jito bundle) — default, recommended │ │\n│ │ Slippage │ 10% (default for bonding curve buys) │ │\n│ └──────────────────┴────────────────────────────────────────────┘ │\n│ │\n│ How it works: │\n│ • buyAmount = 0 → Token creation TX only │\n│ • buyAmount > 0 → Create + Buy in ONE atomic Jito bundle │\n│ • No one can front-run your initial purchase │\n│ • Platform fees are deducted from buyAmount automatically │\n│ │\n│ Balance check: │\n│ • SOL: need buyAmount + 0.02 SOL (fees + rent) │\n│ • BSC: need buyAmount + 0.015 BNB (gas) │\n└────────────────────────────┬─────────────────────────────────────────┘\n │\n ▼\n┌──────────────────────────────────────────────────────────────────────┐\n│ STEP 6: CONFIRMATION TABLE │\n│ │\n│ ┌────────────────┬──────────────────────────────────────────────┐ │\n│ │ Launchpad │ pump.fun │ │\n│ │ Chain │ Solana │ │\n│ │ Token Name │ DogWifHat │ │\n│ │ Ticker │ WIF │ │\n│ │ Description │ The dog with the hat │ │\n│ │ Image │ wif.png (420x420, 85KB) │ │\n│ │ Website │ \u003cyour-website-url> │ │\n│ │ Twitter │ \u003cyour-twitter-url> │ │\n│ │ Telegram │ \u003cyour-telegram-url> │ │\n│ │ ───────────── │ ────────────────────────── │ │\n│ │ Wallet │ 2HNq...ErwW (1.23 SOL) │ │\n│ │ Initial Buy │ 0.5 SOL │ │\n│ │ MEV Protection │ ON (Jito bundle) │ │\n│ │ Slippage │ 10% │ │\n│ │ Priority Fee │ 0.0001 SOL │ │\n│ │ Est. Cost │ ~0.52 SOL (buy + fees + rent) │ │\n│ └────────────────┴──────────────────────────────────────────────┘ │\n│ │\n│ ⚡ Type \"confirm\" to launch. Type \"cancel\" to abort. │\n│ │\n│ ⚠️ This is IRREVERSIBLE. The token will be created on-chain. │\n└────────────────────────────┬─────────────────────────────────────────┘\n │\n ▼\n┌──────────────────────────────────────────────────────────────────────┐\n│ STEP 7: EXECUTION (what happens under the hood) │\n│ │\n│ 7a. Upload image to IPFS (pump.fun free endpoint, Pinata fallback) │\n│ → ipfs://QmXxx... │\n│ │\n│ 7b. Create metadata JSON, upload to IPFS │\n│ { │\n│ \"name\": \"DogWifHat\", │\n│ \"symbol\": \"WIF\", │\n│ \"description\": \"The dog with the hat\", │\n│ \"image\": \"ipfs://QmXxx...\", │\n│ \"twitter\": \"\u003cyour-twitter-url>\", │\n│ \"telegram\": \"\u003cyour-telegram-url>\", │\n│ \"website\": \"\u003cyour-website-url>\" │\n│ } │\n│ → ipfs://QmYyy... (metadata URI) │\n│ │\n│ 7c. Call launchpad adapter: │\n│ pump.fun → PumpPortal /api/trade-local (action: create) │\n│ Bags → SDK createLaunchTransaction() │\n│ Moonit → SDK prepareMintTx() │\n│ LetsBonk → REST API │\n│ Four.Meme → onchainos wallet contract-call (user confirms first) │\n│ Flap → onchainos wallet contract-call (user confirms first) │\n│ │\n│ 7d. If buyAmount > 0: │\n│ • Bundle: [CreateToken IX, Buy IX] → Jito bundle (SOL) │\n│ • Or atomic contract call with value (BSC) │\n│ │\n│ 7e. Sign via onchainos wallet (TEE) │\n│ │\n│ 7f. Submit to chain │\n│ • SOL: submit Jito bundle → wait ~25s │\n│ • BSC: broadcast tx → wait ~3-5s │\n└────────────────────────────┬─────────────────────────────────────────┘\n │\n ▼\n┌──────────────────────────────────────────────────────────────────────┐\n│ STEP 8: RESULT │\n│ │\n│ ✅ Token Launched Successfully! │\n│ │\n│ ┌────────────────┬──────────────────────────────────────────────┐ │\n│ │ Token Name │ DogWifHat (WIF) │ │\n│ │ Token Address │ 7xKXtg2CW87d97TXJSDpbD5jBkhT...pump │ │\n│ │ TX Hash │ 4nF8kJ... │ │\n│ │ Initial Buy │ 0.5 SOL → 12,500,000 WIF │ │\n│ │ Launchpad │ pump.fun │ │\n│ │ Explorer │ https://solscan.io/tx/4nF8kJ... │ │\n│ │ Trade Page │ https://pump.fun/7xKXtg2CW87d... │ │\n│ └────────────────┴──────────────────────────────────────────────┘ │\n│ │\n│ Next steps: │\n│ • \"sell 50% WIF\" — sell via onchainos swap │\n│ • \"buy more WIF\" — buy more via onchainos swap │\n│ • \"check WIF\" — view token info, holders, liquidity │\n│ • Share the trade page link to promote your token │\n└──────────────────────────────────────────────────────────────────────┘","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"AI Agent Startup Interaction Protocol","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"When a user requests to launch a token, the AI Agent must follow the procedure below. Do not skip directly to launch.","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 1: Present Strategy Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Show the user the following:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"一键发币 v1.0 — Multi-Launchpad Token Launch\n\nThis skill creates tokens on bonding curve launchpads with optional bundled initial buy.\nSupports 6 launchpads: pump.fun, Bags.fm, LetsBonk, Moonit (Solana) + Four.Meme, Flap.sh (BSC).\nIPFS metadata upload is handled automatically (pump.fun free endpoint, no API key needed).\nBundled buy creates token + initial buy in ONE atomic Jito bundle — no front-running.\nAll signing via onchainos Agentic Wallet (TEE) — no private keys in code.\n\nCurrent: Paper Mode (DRY_RUN=True) — no real on-chain transactions.\n\nRisk Notice: Token creation is IRREVERSIBLE. You may lose all invested capital.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q1: Choose Launchpad (Optional — default pump.fun)","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"#","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Launchpad","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Chain","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pump.fun","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solana","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Largest SOL launchpad, Raydium migration, Jito MEV protection","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"2","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bags.fm","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solana","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fee sharing, Meteora DBC","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"3","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LetsBonk","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solana","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BONK ecosystem, Raydium migration","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"4","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Moonit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solana","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"80% creator fee share","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Four.Meme","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BSC","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Largest BSC launchpad, PancakeSwap migration","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"6","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Flap.sh","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BSC","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tax tokens, vanity addresses, PCS V3","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"If user doesn't specify → default to pump.fun.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q2: Token Details (Required)","type":"text"}]},{"type":"paragraph","content":[{"text":"Collect from user:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Name","type":"text","marks":[{"type":"strong"}]},{"text":" — Token name (e.g., \"MoonDog\") [Required]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Symbol","type":"text","marks":[{"type":"strong"}]},{"text":" — Ticker (e.g., \"MDOG\") [Required]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Description","type":"text","marks":[{"type":"strong"}]},{"text":" — Short description [Required]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Image","type":"text","marks":[{"type":"strong"}]},{"text":" — File path, URL, base64, or data URI [Required]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Website","type":"text","marks":[{"type":"strong"}]},{"text":" — Project URL [Optional]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Twitter / X","type":"text","marks":[{"type":"strong"}]},{"text":" — Twitter URL [Optional]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Telegram","type":"text","marks":[{"type":"strong"}]},{"text":" — Telegram URL [Optional]","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"If user provides all in one message (e.g., \"launch MoonDog MDOG on pump.fun, image is /tmp/dog.png\"), extract directly — don't re-ask.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q3: Bundled Initial Buy?","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A. Create only","type":"text","marks":[{"type":"strong"}]},{"text":" (buy_amount = 0) — just create the token, no initial purchase","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"B. Buy at launch","type":"text","marks":[{"type":"strong"}]},{"text":" — specify amount in SOL/BNB (e.g., 0.1 SOL)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create + Buy bundled in ONE atomic Jito bundle (Solana) or contract call (BSC)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No one can front-run your initial purchase","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Slippage: 10% default (configurable)","type":"text"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q4: Paper Mode or Live Mode?","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A. Paper Mode","type":"text","marks":[{"type":"strong"}]},{"text":" (default, recommended for first use) → ","type":"text"},{"text":"DRY_RUN = True","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Simulates the entire flow, no real on-chain TX","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"B. Live Mode","type":"text","marks":[{"type":"strong"}]},{"text":" → ","type":"text"},{"text":"DRY_RUN = False","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm with user: \"Live Mode will create a REAL token on-chain. This is IRREVERSIBLE. Confirm?\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User confirms → set ","type":"text"},{"text":"DRY_RUN = False","type":"text","marks":[{"type":"code_inline"}]},{"text":" in config.py","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User declines → fall back to Paper Mode","type":"text"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Launch","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Modify ","type":"text"},{"text":"config.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" based on user responses (launchpad, DRY_RUN mode)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check prerequisites: ","type":"text"},{"text":"onchainos --version","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"onchainos wallet status","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Install dependencies: ","type":"text"},{"text":"pip install -r requirements.txt","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Start dashboard: ","type":"text"},{"text":"python3 token_launch.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" (runs in background, serves at ","type":"text"},{"text":"http://localhost:3245","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Show confirmation summary table (name, symbol, launchpad, buy amount, wallet, balance, mode)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wait for user confirmation (\"confirm\" to launch, \"cancel\" to abort)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Execute via ","type":"text"},{"text":"quick_launch()","type":"text","marks":[{"type":"code_inline"}]},{"text":" — one call handles everything","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Show result: token address, TX hash, explorer link, trade page URL","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Show Dashboard link: ","type":"text"},{"text":"http://localhost:3245","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Special Cases","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User explicitly says \"just launch it\" or gives all details upfront → Extract params, show confirmation, launch (skip Q1-Q4 if info is complete)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User says \"use defaults\" → pump.fun, Paper Mode, no initial buy, but still need name/symbol/description/image","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Returning user (previous launch in conversation) → Remind of previous config, ask whether to reuse","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Execution Rules","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Primary Entry Point: ","type":"text"},{"text":"quick_launch()","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"One call does everything — wallet, IPFS, signing, broadcast, record-keeping:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"# token_launch.py auto-adds its directory to sys.path, so just point to the skill folder:\nimport sys, os\nsys.path.insert(0, os.path.expanduser(\"~/path/to/Token Launch\"))\nfrom token_launch import quick_launch\n\n# Minimal — just name, symbol, description, image:\nresult = await quick_launch(\"MoonDog\", \"MDOG\", \"a good boy\", \"/path/to/dog.png\")\n\n# Full options:\nresult = await quick_launch(\n \"MoonDog\", \"MDOG\", \"a good boy\", \"\u003cyour-website-url>/dog.png\",\n launchpad=\"pumpfun\", # pumpfun | bags | letsbonk | moonit | fourmeme | flap\n buy_amount=0.1, # SOL/BNB — 0 = create only\n website=\"https://moondog.xyz\",\n twitter=\"https://twitter.com/moondog\",\n telegram=\"https://t.me/moondog\",\n)\n\n# result.success, result.token_address, result.tx_hash, result.explorer_url","type":"text"}]},{"type":"paragraph","content":[{"text":"Image input","type":"text","marks":[{"type":"strong"}]},{"text":" — accepts any of:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Local file path: ","type":"text"},{"text":"\"/tmp/dog.png\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"URL: ","type":"text"},{"text":"\"\u003cyour-website-url>/dog.png\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Base64 data URI: ","type":"text"},{"text":"\"data:image/png;base64,iVBOR…\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Raw base64 string","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"quick_launch()","type":"text","marks":[{"type":"code_inline"}]},{"text":" handles everything automatically:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wallet login check + address resolution (cached after first call)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Balance check (reject early if insufficient)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Image normalization (download URL / decode base64 if needed)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"IPFS upload (pump.fun free endpoint first, Pinata fallback)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirmation display (shows all params in a summary box)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Launch execution via the appropriate adapter","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Record saved to ","type":"text"},{"text":"state/launches.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Lark notification (if ","type":"text"},{"text":"LARK_WEBHOOK","type":"text","marks":[{"type":"code_inline"}]},{"text":" set)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Configuration","type":"text"}]},{"type":"paragraph","content":[{"text":"config.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" controls all defaults:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DRY_RUN = True","type":"text","marks":[{"type":"code_inline"}]},{"text":" → simulate (no on-chain TX). Set ","type":"text"},{"text":"False","type":"text","marks":[{"type":"code_inline"}]},{"text":" for real launches.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DEFAULT_LAUNCHPAD = \"pumpfun\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" → default when user doesn't specify","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CONFIRM_REQUIRED = True","type":"text","marks":[{"type":"code_inline"}]},{"text":" → show confirmation before launch","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Image Validation","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Accepted formats: PNG, JPG, GIF, WEBP","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Max size: 5 MB","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Recommended: square (1:1 ratio), minimum 200x200","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"IPFS Upload","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Primary","type":"text","marks":[{"type":"strong"}]},{"text":": pump.fun ","type":"text"},{"text":"/api/ipfs","type":"text","marks":[{"type":"code_inline"}]},{"text":" — free, no API key needed, one call uploads image + creates Metaplex metadata","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fallback","type":"text","marks":[{"type":"strong"}]},{"text":": Pinata — requires ","type":"text"},{"text":"PINATA_JWT","type":"text","marks":[{"type":"code_inline"}]},{"text":" env var","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No setup required for the primary path","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Safety","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ALWAYS show confirmation summary before execution","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NEVER auto-execute — token creation is irreversible","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If balance insufficient → reject with clear message, do NOT proceed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If IPFS upload fails → abort with error","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If on-chain TX fails → show TX hash + error, do NOT retry","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Post-Launch","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Record saved to ","type":"text"},{"text":"state/launches.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Explorer link + trade page URL returned in result","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Lark webhook notification (if ","type":"text"},{"text":"LARK_WEBHOOK","type":"text","marks":[{"type":"code_inline"}]},{"text":" env is set)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Post-launch monitor available: ","type":"text"},{"text":"python3 post_launch.py \u003ctoken_address> --refresh 10","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Launchpad Adapter Specs","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"pump.fun (via PumpPortal)","type":"text"}]},{"type":"paragraph","content":[{"text":"API Base","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"https://pumpportal.fun","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Token Creation + Buy (bundled)","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"POST /api/trade-local\n{\n \"action\": \"create\",\n \"tokenMetadata\": {\n \"name\": \"DogWifHat\",\n \"symbol\": \"WIF\",\n \"uri\": \"https://gateway.pinata.cloud/ipfs/QmYyy...\"\n },\n \"mint\": \"\u003cbase58_mint_keypair>\",\n \"denominatedInSol\": \"true\",\n \"amount\": 0.5,\n \"slippage\": 10,\n \"priorityFee\": 0.0001,\n \"pool\": \"pump\"\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Response","type":"text","marks":[{"type":"strong"}]},{"text":": unsigned transaction bytes (with mint keypair signature embedded by PumpPortal)","type":"text"}]},{"type":"paragraph","content":[{"text":"Signing flow","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mint keypair generated locally (pump.fun protocol requirement)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mint keypair secret passed to PumpPortal -- PumpPortal embeds the mint signature","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Unsigned TX (needs only fee payer signature) sent to ","type":"text"},{"text":"onchainos wallet contract-call --unsigned-tx","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"TEE wallet adds fee payer signature and broadcasts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optional ","type":"text"},{"text":"--mev-protection","type":"text","marks":[{"type":"code_inline"}]},{"text":" uses Jito bundle for front-run protection","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Notes","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mint keypair is randomly generated client-side (protocol requirement)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User wallet is the fee payer -- no ephemeral keypairs needed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"IPFS upload via pump.fun ","type":"text"},{"text":"/api/ipfs","type":"text","marks":[{"type":"code_inline"}]},{"text":" (free, no API key) with Pinata fallback","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No platform fee on creation, standard fee on dev buy","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pool options: \"pump\" (default) or \"bonk\" (LetsBonk pool)","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Bags.fm","type":"text"}]},{"type":"paragraph","content":[{"text":"SDK","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"@bags-fm/sdk","type":"text","marks":[{"type":"code_inline"}]},{"text":" (TypeScript) — we call via REST endpoints","type":"text"}]},{"type":"paragraph","content":[{"text":"Flow","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"POST /token-launch/create-token-info","type":"text","marks":[{"type":"code_inline"}]},{"text":" — upload metadata (name, symbol, desc, image, socials)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"POST /fee-share/config","type":"text","marks":[{"type":"code_inline"}]},{"text":" — create fee share config (creator BPS, optional co-earners)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"POST /token-launch/create-launch-transaction","type":"text","marks":[{"type":"code_inline"}]},{"text":" — create launch TX with ","type":"text"},{"text":"initialBuyLamports","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Fee Sharing","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Creator must set their BPS explicitly (no default allocation)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Total must = 10,000 bps (100%)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Max 100 fee earners per token","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Supports social username lookups (Twitter, GitHub, Kick)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Notes","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Uses Meteora Dynamic Bonding Curve","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Bags handles IPFS upload internally via their API","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No external Pinata needed (optional)","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Moonit","type":"text"}]},{"type":"paragraph","content":[{"text":"SDK","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"@moonit/sdk","type":"text","marks":[{"type":"code_inline"}]},{"text":" (TypeScript) — Python wrapper calls SDK methods","type":"text"}]},{"type":"paragraph","content":[{"text":"Flow","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"prepareMintTx()","type":"text","marks":[{"type":"code_inline"}]},{"text":" — builds mint transaction with token metadata","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sign transaction","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"submitMintTx()","type":"text","marks":[{"type":"code_inline"}]},{"text":" — submit signed transaction","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Notes","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Creator earns 80% of all trading fees","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Supports Raydium and Meteora V2 migration targets","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Built-in IPFS upload in SDK","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"LetsBonk","type":"text"}]},{"type":"paragraph","content":[{"text":"MCP Server","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"bonk-mcp","type":"text","marks":[{"type":"code_inline"}]},{"text":" — or direct REST API","type":"text"}]},{"type":"paragraph","content":[{"text":"Flow","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create token via API (name, symbol, metadata URI)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optional initial buy","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Submit to Solana","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Notes","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Part of BONK ecosystem","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Migrates to Raydium after bonding curve completion","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pool option ","type":"text"},{"text":"pool: \"bonk\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" also available via PumpPortal","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Four.Meme (BSC)","type":"text"}]},{"type":"paragraph","content":[{"text":"Method","type":"text","marks":[{"type":"strong"}]},{"text":": Direct contract interaction via ","type":"text"},{"text":"onchainos wallet contract-call","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"IMPORTANT","type":"text","marks":[{"type":"strong"}]},{"text":": The agent MUST display all transaction parameters and receive explicit user confirmation (typing \"confirm\") BEFORE executing any contract call. Never auto-execute.","type":"text"}]},{"type":"paragraph","content":[{"text":"Flow","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Upload image to IPFS (pump.fun free endpoint, Pinata fallback)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create metadata (description, image CID, socials)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Display full transaction summary and wait for user to type \"confirm\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Call Four.Meme factory contract: ","type":"text"},{"text":"createToken(name, symbol, metadataURI, ...)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Include ","type":"text"},{"text":"msg.value","type":"text","marks":[{"type":"code_inline"}]},{"text":" for initial buy (if buyAmount > 0)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Notes","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Image upload handled by Four.Meme platform internally (if using their web UI)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For programmatic: use Pinata, pass CID","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Categories: Meme, AI, DeFi, Games, Infra, De-Sci, Social, Depin, Charity, Others","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No tax token support on Four.Meme","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Flap.sh (BSC)","type":"text"}]},{"type":"paragraph","content":[{"text":"Method","type":"text","marks":[{"type":"strong"}]},{"text":": Direct contract interaction via ","type":"text"},{"text":"onchainos wallet contract-call","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"IMPORTANT","type":"text","marks":[{"type":"strong"}]},{"text":": The agent MUST display all transaction parameters and receive explicit user confirmation (typing \"confirm\") BEFORE executing any contract call. Never auto-execute.","type":"text"}]},{"type":"paragraph","content":[{"text":"Portal Contract","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0","type":"text","marks":[{"type":"code_inline"}]},{"text":" (BNB Mainnet)","type":"text"}]},{"type":"paragraph","content":[{"text":"Function","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"newTokenV6(NewTokenV6Params)","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Parameters","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"name: string — Token name\nsymbol: string — Token symbol\nmeta: string — IPFS CID of metadata\ndexThresh: uint8 — DEX listing threshold type\nsalt: bytes32 — Vanity salt (0x0 for random)\nmigratorType: uint8 — V2_MIGRATOR or V3_MIGRATOR\nquoteToken: address — address(0) for native BNB\nquoteAmt: uint256 — Initial buy amount\nbeneficiary: address — Tax recipient\nbuyTaxRate: uint16 — Buy tax (basis points)\nsellTaxRate: uint16 — Sell tax (basis points)\ntaxDuration: uint256 — How long tax applies (seconds)\nantiFarmerDuration: uint256 — Anti-dump duration (seconds)\nmktBps: uint16 — Marketing allocation from tax\ndeflationBps: uint16 — Burn allocation from tax\ndividendBps: uint16 — Dividend allocation from tax\nlpBps: uint16 — LP allocation from tax\ntokenVersion: uint8 — 6 (TOKEN_TAXED_V3, recommended)","type":"text"}]},{"type":"paragraph","content":[{"text":"Notes","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Supports asymmetric buy/sell tax rates","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Vanity token addresses via ","type":"text"},{"text":"salt","type":"text","marks":[{"type":"code_inline"}]},{"text":" parameter","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tax splits: mktBps + deflationBps + dividendBps + lpBps = total tax allocation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DEX migration to PancakeSwap V2 or V3","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Dashboard","type":"text"}]},{"type":"paragraph","content":[{"text":"Port","type":"text","marks":[{"type":"strong"}]},{"text":": 3245","type":"text"}]},{"type":"paragraph","content":[{"text":"Features","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Numbered timeline list (newest at top) with token logos","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Bonding curve progress bar (live-updating for active tokens)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Live stats: price, market cap, holders, buy/sell volume","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wallet balance display and mode indicator (DRY RUN / LIVE)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Social links, explorer links, and launchpad chips","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Config Reference","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"config.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" for all configurable parameters with descriptions.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Start Examples","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Launch on pump.fun (create only, no buy)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User: \"Launch a token called MoonCat, ticker MCAT, on pump.fun\"\n→ Skill collects: description, image\n→ Uploads to IPFS\n→ Calls PumpPortal create (buyAmount = 0)\n→ Returns token address","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Launch on pump.fun with bundled buy","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User: \"发币 CoolDog, ticker CDOG, buy 0.5 SOL\"\n→ Skill collects: description, image\n→ Uploads to IPFS\n→ Bundles: create TX + buy 0.5 SOL TX → Jito bundle\n→ Returns token address + initial position","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Launch on Flap.sh with tax token","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User: \"Create a tax token on BSC via Flap, 5% buy tax, 3% sell tax\"\n→ Skill collects: name, ticker, desc, image, tax config\n→ Uploads metadata to IPFS\n→ Calls Flap portal newTokenV6() with tax params\n→ Returns token address on BSC","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Launch on Bags.fm with fee sharing","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User: \"Launch on Bags, share 50% fees with my partner\"\n→ Skill collects: name, ticker, desc, image, partner address\n→ Creates fee share config (creator 5000 bps, partner 5000 bps)\n→ Creates launch TX\n→ Returns token address + fee share config","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"one-click-token-launch","author":"@skillopedia","source":{"stars":11,"repo_name":"plugin-store","origin_url":"https://github.com/okx/plugin-store/blob/HEAD/skills/one-click-token-launch/SKILL.md","repo_owner":"okx","body_sha256":"686bc01304ef1ff8b8581db0ae51738a07731d0979b8d90e1b4c0879287a98cf","cluster_key":"28cd3f4b61bfffb64b3c7c859d8fe9af59e3a6744338da80d4aa19e265480db3","clean_bundle":{"format":"clean-skill-bundle-v1","source":"okx/plugin-store/skills/one-click-token-launch/SKILL.md","attachments":[{"id":"c8597e20-f1bb-56c3-a476-53d22b5b45b9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c8597e20-f1bb-56c3-a476-53d22b5b45b9/attachment.json","path":".claude-plugin/plugin.json","size":521,"sha256":"563811e42256ebc5934779567cc16dd6bd46f20b5c2bc614154efab865703c7e","contentType":"application/json; charset=utf-8"},{"id":"0f45260b-9684-582e-bd17-a736c7323f23","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0f45260b-9684-582e-bd17-a736c7323f23/attachment","path":".gitignore","size":242,"sha256":"aeef3f1d19ea4ddb735ff8e7fd0a7e09c97c340b970be55f718dbec359dcb8a5","contentType":"text/plain; charset=utf-8"},{"id":"ff3910e1-2477-50a1-8106-c7b10b1bad00","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ff3910e1-2477-50a1-8106-c7b10b1bad00/attachment.md","path":"README.md","size":2300,"sha256":"669a64a65a0a16479b6fcd42d2dd93b2a0f3298e16d8a7ab3999da7fe7decc4b","contentType":"text/markdown; charset=utf-8"},{"id":"878c4445-a781-5b70-a42b-dbe8f3a3f246","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/878c4445-a781-5b70-a42b-dbe8f3a3f246/attachment.md","path":"SUMMARY.md","size":1802,"sha256":"a8e350e56c53273a78cd51ca9a9456ac8fc4881704e80d4a1a97ddc366559b8c","contentType":"text/markdown; charset=utf-8"},{"id":"3c7ad436-ac07-514c-b800-699992ae482d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3c7ad436-ac07-514c-b800-699992ae482d/attachment.py","path":"config.py","size":7696,"sha256":"9135b77380d8629f2eba7c6d7e9b6b9d85a753b274cfde582d270abc351c0511","contentType":"text/x-python; charset=utf-8"},{"id":"54be2593-22b9-5da4-81c0-55b72ccef38f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/54be2593-22b9-5da4-81c0-55b72ccef38f/attachment.html","path":"dashboard.html","size":17983,"sha256":"9c7f4ad6de7fc62fa96139297ffc38132c0fe04fd7f779b78edbe4342dece56a","contentType":"text/html; charset=utf-8"},{"id":"7ddecfb6-956e-5c56-a0b2-3d23f7b1a4b1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7ddecfb6-956e-5c56-a0b2-3d23f7b1a4b1/attachment.py","path":"ipfs.py","size":11058,"sha256":"d919349f2cb979b8b848654c5cfbc8edbd12fb5cc24731b01bb5e3fc96f1fb77","contentType":"text/x-python; charset=utf-8"},{"id":"ceb8ca6f-e98a-50e2-9c81-b30f771e74f0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ceb8ca6f-e98a-50e2-9c81-b30f771e74f0/attachment.py","path":"launchpads/__init__.py","size":1129,"sha256":"cae9a6914e1c42255a98757d993cf5a949cb6e0fc5e5d0988ed54cc8a1a9b901","contentType":"text/x-python; charset=utf-8"},{"id":"0c1f8148-8ffb-5c5c-befc-af011f24276f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0c1f8148-8ffb-5c5c-befc-af011f24276f/attachment.py","path":"launchpads/bags.py","size":9595,"sha256":"7b9292ad6a2247782684580fbc3aa7d5ee91739e577552638269e59552888297","contentType":"text/x-python; charset=utf-8"},{"id":"ae8cd97a-bd99-540a-9ad4-76cfa609a2e7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ae8cd97a-bd99-540a-9ad4-76cfa609a2e7/attachment.py","path":"launchpads/base.py","size":4750,"sha256":"63977d564511c3738d5b902517d9b0cafe8b52a1395c333c54b01ea96aca8d33","contentType":"text/x-python; charset=utf-8"},{"id":"d48c1c58-301e-5dd6-8c8b-31cd5e229d35","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d48c1c58-301e-5dd6-8c8b-31cd5e229d35/attachment.py","path":"launchpads/flap.py","size":13984,"sha256":"24a204bc5aa3aeef65e04ba57f16db2d7d937e7f680bcfe5394d3fc87a14a9bd","contentType":"text/x-python; charset=utf-8"},{"id":"d313e7ac-c227-54a7-a5fa-a23459b6c118","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d313e7ac-c227-54a7-a5fa-a23459b6c118/attachment.py","path":"launchpads/fourmeme.py","size":7498,"sha256":"03b4b828c9c8a7c550faa20adf74cbf323c24e90eff9117b9ef38c69281b1343","contentType":"text/x-python; charset=utf-8"},{"id":"9f12d966-83f6-5eb9-bf84-f1f75c63a7b9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9f12d966-83f6-5eb9-bf84-f1f75c63a7b9/attachment.py","path":"launchpads/letsbonk.py","size":7617,"sha256":"24aed58812f5ae2f99fb472c345b3661315a8e20144abe5df7500aa10fff337b","contentType":"text/x-python; charset=utf-8"},{"id":"e1cd5534-9ffd-57d0-9023-9b573153db44","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e1cd5534-9ffd-57d0-9023-9b573153db44/attachment.py","path":"launchpads/moonit.py","size":6757,"sha256":"377ad6456991282045a885d314999bf59a80f30413ea69108915226a843b9a37","contentType":"text/x-python; charset=utf-8"},{"id":"32eabe5e-4cf7-5e97-a982-58f36939e0f0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/32eabe5e-4cf7-5e97-a982-58f36939e0f0/attachment.py","path":"launchpads/pumpfun.py","size":8055,"sha256":"96f76b80b31476153064fa2487b501d17e77a4d868c7fa219f4b883e2861ea6e","contentType":"text/x-python; charset=utf-8"},{"id":"62084775-cd41-52b2-847b-d07c0845c19b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/62084775-cd41-52b2-847b-d07c0845c19b/attachment.yaml","path":"plugin.yaml","size":614,"sha256":"6313eb941ba1001e5ac4a4a4be5f62fcf66284420377cc9c2981d2c781cc4c8d","contentType":"application/yaml; charset=utf-8"},{"id":"6b031f14-ea34-5839-abb7-df2e00f3098d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6b031f14-ea34-5839-abb7-df2e00f3098d/attachment.py","path":"post_launch.py","size":23015,"sha256":"7cc8a6071b461655a26d7088e9405f5d1d52dad3034d96c4247ab5b6fd0b7605","contentType":"text/x-python; charset=utf-8"},{"id":"8e3e2bab-db6a-516e-8afc-119d72b31dd7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8e3e2bab-db6a-516e-8afc-119d72b31dd7/attachment.txt","path":"requirements.txt","size":53,"sha256":"6e9722aa77c55d5f62be153343f82780d6eb6cb7cb3cda8fbad7ab860d795c7b","contentType":"text/plain; charset=utf-8"},{"id":"d7eda6af-2b4b-57ad-831b-371ff36dfca8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d7eda6af-2b4b-57ad-831b-371ff36dfca8/attachment.py","path":"token_launch.py","size":28215,"sha256":"2a8704dd35b8b4bc4ca65a7290cbc940afd8bdb2a76eed35cebc2229bbe775a6","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"87e26f51f4e7e4e88182471f51ed5c3cb5ad53d3ca38b5d4ef40de9b5dfdef14","attachment_count":19,"text_attachments":19,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/one-click-token-launch/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"data-analytics","category_label":"Data"},"exact_dupes_collapsed_into_this":0},"updated":"2026-04-13T00:00:00.000Z","version":"v1","category":"data-analytics","triggers":"一键发币、发币、创建代币、launch token、create token、deploy token、mint token、 launch meme coin、pump.fun launch、create a new token on Solana or BSC\n","import_tag":"clean-skills-v1","description":"One-click multi-launchpad token creation with bundled buy, IPFS metadata, MEV protection across 6 launchpads on Solana and BSC"}},"renderedAt":1782980655584}

一键发币 v1.0 — Multi-Launchpad Token Launch One-click token creation with optional bundled initial buy across 6+ launchpads. --- Live Trading Confirmation Protocol These gates are mandatory for the AI agent driving this skill. Before any call that signs or broadcasts an on-chain transaction (any , , , or any internal write code path that ends in a real on-chain submission), ALL of the following must be true: 1. Paper / preview mode is the default. Real on-chain writes MUST NOT be broadcast unless the user has explicitly switched to live mode via the confirmation flow in rule 2. If no explicit li…