ce / token_launcher.py
josephrw's picture
Upload token_launcher.py with huggingface_hub
e001da5 verified
Raw
History Blame Contribute Delete
14.4 kB
#!/usr/bin/env python3
"""
AirMicroDrip Autonomous Token Launcher
Creates a Solana wallet, requests devnet SOL, and mints a new SPL token
automatically. Stores the keypair for user backup.
"""
import os
import json
import sqlite3
import requests
import base64
import time
from typing import Dict, Optional, List
from datetime import datetime
from pathlib import Path
# Try to import solders for real transaction signing
try:
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.system_program import ID as SYSTEM_PROGRAM_ID
SOLDERS_AVAILABLE = True
except ImportError:
SOLDERS_AVAILABLE = False
print("[token_launcher] solders not available - using RPC-only mode")
SOLANA_RPC_URL = os.environ.get("SOLANA_RPC_URL", "https://api.devnet.solana.com")
TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
MINT_LEN = 82 # bytes
DB_PATH = os.environ.get("TOKEN_LAUNCH_DB", "token_launch/launch_registry.db")
def _ensure_db():
Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS launches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
wallet_pubkey TEXT UNIQUE,
wallet_secret TEXT,
mint_address TEXT UNIQUE,
token_name TEXT,
token_symbol TEXT,
decimals INTEGER,
total_supply INTEGER,
network TEXT,
created_at TEXT,
status TEXT,
backup_downloaded INTEGER DEFAULT 0,
tx_signature TEXT
)
""")
conn.commit()
conn.close()
def _rpc_call(method: str, params: list) -> Optional[dict]:
try:
resp = requests.post(
SOLANA_RPC_URL,
json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params},
headers={"Content-Type": "application/json"},
timeout=30,
)
data = resp.json()
return data.get("result")
except Exception as e:
print(f"[token_launcher] RPC error: {e}")
return None
def generate_wallet() -> Dict:
"""Generate a new Solana keypair."""
if not SOLDERS_AVAILABLE:
return {"status": "error", "message": "solders library not installed"}
kp = Keypair()
pubkey = str(kp.pubkey())
# solders keypair bytes: first 32 = secret, last 32 = pubkey
secret_bytes = bytes(kp)[:32]
# Encode as base64 for storage (safer than base58 for raw bytes)
secret_b64 = base64.b64encode(secret_bytes).decode("utf-8")
return {
"status": "ok",
"pubkey": pubkey,
"secret": secret_b64,
"keypair": kp,
}
def request_airdrop(pubkey: str, lamports: int = 1_000_000_000) -> Optional[str]:
"""Request devnet SOL airdrop (1 SOL = 1B lamports)."""
sig = _rpc_call("requestAirdrop", [pubkey, lamports])
if sig:
print(f"[token_launcher] Airdrop requested: {sig}")
# Wait for confirmation
for _ in range(15):
status = _rpc_call("getSignatureStatuses", [[sig]])
if status and status.get("value") and status["value"][0]:
if status["value"][0].get("confirmationStatus") in ("confirmed", "finalized"):
print(f"[token_launcher] Airdrop confirmed")
return sig
time.sleep(2)
return None
def get_minimum_balance_for_rent_exemption(data_len: int) -> int:
"""Get rent exemption in lamports."""
result = _rpc_call("getMinimumBalanceForRentExemption", [data_len])
return result or 1461600 # fallback for mint
def _send_raw_transaction(tx_base64: str) -> Optional[str]:
"""Send a base64-encoded transaction."""
return _rpc_call("sendTransaction", [tx_base64, {"encoding": "base64", "skipPreflight": False}])
def create_token_mint(
wallet_keypair,
decimals: int = 9,
token_name: str = "AirMicroDrip",
token_symbol: str = "DRIP",
) -> Dict:
"""Create a new SPL token mint on Solana using raw RPC calls."""
if not SOLDERS_AVAILABLE:
return {"status": "error", "message": "solders library not installed"}
payer = wallet_keypair
payer_pubkey = payer.pubkey()
# Generate new mint keypair
mint_kp = Keypair()
mint_pubkey = mint_kp.pubkey()
# Get rent exemption
rent_lamports = get_minimum_balance_for_rent_exemption(MINT_LEN)
# Get recent blockhash
blockhash_result = _rpc_call("getLatestBlockhash", [])
if not blockhash_result:
return {"status": "error", "message": "Failed to get recent blockhash"}
blockhash = blockhash_result["value"]["blockhash"]
try:
# Build transaction manually using solders Message + Transaction
# Import here to handle version differences gracefully
from solders.system_program import CreateAccountParams, create_account
from solders.instruction import Instruction, AccountMeta
from solders.message import Message
from solders.transaction import Transaction
# Create account instruction
create_acc_ix = create_account(
CreateAccountParams(
from_pubkey=payer_pubkey,
to_pubkey=mint_pubkey,
lamports=rent_lamports,
space=MINT_LEN,
owner=Pubkey.from_string(TOKEN_PROGRAM_ID),
)
)
# Initialize mint instruction (manually encoded)
# Instruction 0 = InitializeMint
init_data = bytes([0, decimals, 1]) + bytes(payer_pubkey) + bytes([0])
init_mint_ix = Instruction(
program_id=Pubkey.from_string(TOKEN_PROGRAM_ID),
accounts=[
AccountMeta(mint_pubkey, is_signer=False, is_writable=True),
AccountMeta(payer_pubkey, is_signer=False, is_writable=False),
],
data=init_data,
)
# Build legacy transaction (not versioned - more compatible)
msg = Message.new_with_blockhash(
[create_acc_ix, init_mint_ix],
payer_pubkey,
blockhash,
)
tx = Transaction([payer, mint_kp], msg, blockhash)
tx_base64 = base64.b64encode(tx.serialize()).decode("utf-8")
except Exception as e:
# Fallback: try simpler approach if solders API differs
print(f"[token_launcher] Transaction build warning: {e}")
return {
"status": "wallet_ready",
"message": "Wallet created and funded, but automated mint creation requires spl-token CLI. Use 'spl-token create-token' with the backed-up keypair.",
"wallet_pubkey": str(payer_pubkey),
"next_step": "Install spl-token CLI and run: spl-token create-token --fee-payer <backup>",
}
# Send transaction
sig = _send_raw_transaction(tx_base64)
if not sig:
return {"status": "error", "message": "Failed to send create-mint transaction"}
print(f"[token_launcher] Mint tx sent: {sig}")
# Wait for confirmation
for _ in range(20):
status = _rpc_call("getSignatureStatuses", [[sig]])
if status and status.get("value") and status["value"][0]:
if status["value"][0].get("confirmationStatus") in ("confirmed", "finalized"):
if not status["value"][0].get("err"):
print(f"[token_launcher] Mint confirmed: {mint_pubkey}")
return {
"status": "ok",
"mint_address": str(mint_pubkey),
"tx_signature": sig,
"decimals": decimals,
}
else:
return {"status": "error", "message": f"Transaction failed: {status['value'][0]['err']}"}
time.sleep(2)
return {"status": "error", "message": "Transaction confirmation timeout"}
def autonomously_create_token(
token_name: str = "AirMicroDrip",
token_symbol: str = "DRIP",
decimals: int = 9,
) -> Dict:
"""
Full autonomous flow:
1. Generate wallet
2. Request airdrop
3. Create token mint
4. Store in DB
5. Return mint address + backup info
"""
_ensure_db()
# 1. Generate wallet
wallet = generate_wallet()
if wallet["status"] != "ok":
return wallet
kp = wallet["keypair"]
pubkey = wallet["pubkey"]
secret = wallet["secret"]
# 2. Request airdrop
airdrop_sig = request_airdrop(pubkey, 2_000_000_000) # 2 SOL
if not airdrop_sig:
# Airdrop failed (devnet faucet rate-limited) — store wallet for manual funding
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO launches
(wallet_pubkey, wallet_secret, mint_address, token_name, token_symbol, decimals, total_supply, network, created_at, status, tx_signature)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
pubkey, secret, None, token_name, token_symbol, decimals,
1_000_000_000, "solana-devnet", datetime.utcnow().isoformat(), "wallet_created_needs_funding", None
))
conn.commit()
conn.close()
return {
"status": "wallet_created_needs_funding",
"message": "Wallet created but devnet airdrop failed (faucet rate-limited). Fund the wallet manually, then retry.",
"wallet_pubkey": pubkey,
"mint_address": None,
"decimals": decimals,
"total_supply": 1_000_000_000,
"network": "solana-devnet",
"backup_url": "/api/token/backup",
"fund_url": f"https://faucet.solana.com/?address={pubkey}",
"explorer_url": f"https://explorer.solana.com/address/{pubkey}?cluster=devnet",
"warning": "Download your keypair backup immediately. Then fund this wallet via the Solana devnet faucet.",
}
# 3. Create mint
result = create_token_mint(kp, decimals, token_name, token_symbol)
if result["status"] not in ("ok", "wallet_ready"):
return result
if result["status"] == "wallet_ready":
# Mint creation requires manual step — store wallet
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO launches
(wallet_pubkey, wallet_secret, mint_address, token_name, token_symbol, decimals, total_supply, network, created_at, status, tx_signature)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
pubkey, secret, None, token_name, token_symbol, decimals,
1_000_000_000, "solana-devnet", datetime.utcnow().isoformat(), "wallet_ready", None
))
conn.commit()
conn.close()
return {
"status": "wallet_ready",
"message": "Wallet created and funded. SPL token mint creation requires spl-token CLI in this environment.",
"wallet_pubkey": pubkey,
"mint_address": None,
"backup_url": "/api/token/backup",
"next_step": "Install spl-token CLI and run: spl-token create-token --fee-payer <backup>",
}
mint_address = result["mint_address"]
tx_sig = result["tx_signature"]
# 4. Store in DB
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO launches
(wallet_pubkey, wallet_secret, mint_address, token_name, token_symbol, decimals, total_supply, network, created_at, status, tx_signature)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
pubkey, secret, mint_address, token_name, token_symbol, decimals,
1_000_000_000, "solana-devnet", datetime.utcnow().isoformat(), "minted", tx_sig
))
conn.commit()
conn.close()
return {
"status": "ok",
"message": f"Token '{token_name}' ({token_symbol}) created successfully on devnet.",
"wallet_pubkey": pubkey,
"mint_address": mint_address,
"decimals": decimals,
"total_supply": 1_000_000_000,
"network": "solana-devnet",
"tx_signature": tx_sig,
"backup_url": "/api/token/backup",
"warning": "Download and store your keypair backup immediately. It is the only way to recover this wallet.",
}
def get_launch_status() -> Optional[Dict]:
"""Get the most recent token launch status."""
_ensure_db()
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
SELECT wallet_pubkey, mint_address, token_name, token_symbol, decimals, total_supply,
network, created_at, status, backup_downloaded, tx_signature
FROM launches ORDER BY created_at DESC LIMIT 1
""")
row = cursor.fetchone()
conn.close()
if not row:
return None
return {
"wallet_pubkey": row[0],
"mint_address": row[1],
"token_name": row[2],
"token_symbol": row[3],
"decimals": row[4],
"total_supply": row[5],
"network": row[6],
"created_at": row[7],
"status": row[8],
"backup_downloaded": bool(row[9]),
"tx_signature": row[10],
"explorer_url": f"https://explorer.solana.com/address/{row[1]}?cluster=devnet" if row[1] else None,
}
def get_keypair_backup() -> Optional[Dict]:
"""Get the wallet keypair for backup/download."""
_ensure_db()
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
SELECT wallet_pubkey, wallet_secret, mint_address, token_symbol
FROM launches WHERE status IN ('minted', 'wallet_created_needs_funding', 'wallet_ready')
ORDER BY created_at DESC LIMIT 1
""")
row = cursor.fetchone()
if row:
cursor.execute("UPDATE launches SET backup_downloaded = 1 WHERE wallet_pubkey = ?", (row[0],))
conn.commit()
conn.close()
if not row:
return None
return {
"pubkey": row[0],
"secret": row[1],
"mint_address": row[2],
"token_symbol": row[3],
}
def get_existing_mint() -> Optional[str]:
"""Return the existing mint address if one exists."""
status = get_launch_status()
return status["mint_address"] if status else None
if __name__ == "__main__":
result = autonomously_create_token()
print(json.dumps(result, indent=2))