@hot-fun/hot-fun-ai 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -43,7 +43,7 @@ Never ask the user to paste a private key into chat. All private keys must come
43
43
  ## Conventions (aligned with SKILL.md)
44
44
 
45
45
  1. **Chain**: Solana only.
46
- 2. **Create token**: Single command `hotfun create-token <name> <symbol> <uri>` handles the full flow: call API to get base58 transaction → sign with private key → send to Solana RPC. User provides token image/metadata URI directly (no image upload needed).
46
+ 2. **Create token**: Single command `hotfun create-token <name> <symbol> <image_url> [--description "desc"] [--x-royalty-party "username"]` handles the full flow: call API (with agent_ts + agent_sign authentication) to get base58 transaction → sign with private key → send to Solana RPC.
47
47
  3. **RPC and private key (environment)**:
48
48
  - When **using OpenClaw**: PRIVATE_KEY and SOLANA_RPC_URL are configured in OpenClaw (e.g. `skills.entries["hot-fun-ai"].env` or apiKey); see SKILL.md.
49
49
  - When **not using OpenClaw (standalone)**: set `PRIVATE_KEY` and optionally `SOLANA_RPC_URL` via the process environment — e.g. a `.env` file in the **directory where you run `hotfun`** (the CLI loads it automatically via dotenv) or shell `export`. Do not ask the user to paste a private key in chat.
@@ -58,6 +58,6 @@ npx hotfun <command> [args...]
58
58
 
59
59
  Key commands (full list and parameters in `SKILL.md`):
60
60
 
61
- - `hotfun create-token <name> <symbol> <uri>` – Create token on hot.fun (API → sign → send to Solana).
61
+ - `hotfun create-token <name> <symbol> <image_url> [--description "desc"] [--x-royalty-party "user"]` – Create token on hot.fun (API → sign → send to Solana).
62
62
 
63
63
  Always prefer these CLI commands rather than calling scripts directly, unless `SKILL.md` suggests otherwise.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Safety, user agreement, and detailed agent behavior requirements are defined in
14
14
 
15
15
  When the user needs to create meme tokens on hot.fun (Solana), use the **hot-fun-integration** skill:
16
16
 
17
- - **Create token**: `hotfun create-token <name> <symbol> <uri>` — calls the hot.fun API to get a transaction, signs it with the wallet private key, and sends it to Solana RPC. See `SKILL.md` and `references/api-create-token.md` for full details.
17
+ - **Create token**: `hotfun create-token <name> <symbol> <image_url> [--description "desc"] [--x-royalty-party "user"]` — calls the hot.fun API (with agent authentication) to get a transaction, signs it with the wallet private key, and sends it to Solana RPC. See `SKILL.md` and `references/api-create-token.md` for full details.
18
18
  - **CLI** (after `npm install`): use **`npx hotfun <command> [args...]`**. Run `npx hotfun --help` for all commands.
19
19
 
20
20
  ## Install (project)
@@ -45,7 +45,7 @@ The CLI automatically loads `.env` from the current working directory.
45
45
  ```bash
46
46
  export PRIVATE_KEY=your_solana_private_key
47
47
  export SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
48
- npx hotfun create-token MyToken MTK "https://example.com/metadata.json"
48
+ npx hotfun create-token MyToken MTK "https://example.com/image.png" --description "A cool token"
49
49
  ```
50
50
 
51
51
  - **PRIVATE_KEY**: Required for any command that signs or sends a transaction. Base58 string or JSON array of bytes.
package/bin/hotfun.cjs CHANGED
@@ -34,10 +34,12 @@ function printHelp() {
34
34
  Usage: npx hotfun <command> [args...]
35
35
 
36
36
  Commands:
37
- create-token <name> <symbol> <uri>
37
+ create-token <name> <symbol> <image_url> [options]
38
38
  Create token on hot.fun (API → sign → send). Env: PRIVATE_KEY.
39
+ --description <desc> Token description
40
+ --x-royalty-party <user> X (Twitter) username for royalty party
39
41
 
40
- Env: PRIVATE_KEY, SOLANA_RPC_URL (optional), ROYALTY_PARTY (optional). See SKILL.md for full docs.
42
+ Env: PRIVATE_KEY, SOLANA_RPC_URL (optional). See SKILL.md for full docs.
41
43
  `);
42
44
  }
43
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-fun/hot-fun-ai",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Hot.fun AI skills for creating meme tokens on Solana",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,7 +37,8 @@
37
37
  "dependencies": {
38
38
  "@solana/web3.js": "^1.95.0",
39
39
  "bs58": "^6.0.0",
40
- "dotenv": "^16.0.0"
40
+ "dotenv": "^16.0.0",
41
+ "tweetnacl": "^1.0.3"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@types/node": "^20.0.0",
@@ -8,7 +8,7 @@ allowed-tools:
8
8
  - Bash(npx hotfun *)
9
9
  license: MIT
10
10
  metadata:
11
- {"author":"Hot.fun AI Skill","version":"1.0.0","openclaw":{"requires":{"env":["PRIVATE_KEY"]},"primaryEnv":"PRIVATE_KEY","optionalEnv":["SOLANA_RPC_URL","ROYALTY_PARTY"]}}
11
+ {"author":"Hot.fun AI Skill","version":"1.0.0","openclaw":{"requires":{"env":["PRIVATE_KEY"]},"primaryEnv":"PRIVATE_KEY","optionalEnv":["SOLANA_RPC_URL"]}}
12
12
  ---
13
13
 
14
14
  ## [Agent must follow] User agreement and security notice on first use
@@ -82,33 +82,33 @@ After installation, run commands with `hotfun <command> [args]`. If you use a lo
82
82
 
83
83
  ## Create token flow
84
84
 
85
- ### 1. Ask user for required information (must be done first)
85
+ ### 1. Ask user for required AND optional information (must be done first)
86
86
 
87
- Before calling `create-token`, the Agent **must** ask the user for and confirm:
87
+ Before calling `create-token`, the Agent **must** ask the user for all of the following parameters. For optional parameters, the Agent **must still ask** the user whether they want to provide a value — do not skip them silently. If the user declines or leaves them empty, omit them from the command.
88
88
 
89
89
  | Info | Required | Description |
90
90
  |------|----------|-------------|
91
- | **Token name** (name) | Yes | Full token name |
92
- | **Token symbol** (symbol) | Yes | e.g. MTK, DOGE |
93
- | **URI** (uri) | Yes | Token metadata / image URL (e.g. IPFS link) |
94
-
95
- Optional env: `ROYALTY_PARTY` (royalty recipient address; defaults to system program = no royalty).
91
+ | **Token name** (name) | Yes | Full token name, max 255 chars |
92
+ | **Token symbol** (symbol) | Yes | e.g. MTK, DOGE, max 32 chars |
93
+ | **Image URL** (image_url) | Yes | Token image URL (e.g. IPFS link) |
94
+ | **Description** (description) | No (but must ask) | Token description, e.g. a short intro for the token |
95
+ | **X Royalty Party** (x_royalty_party) | No (but must ask) | X (Twitter) username for royalty party |
96
96
 
97
97
  ### 2. Technical flow (done by create-token)
98
98
 
99
99
  After collecting the above, execute:
100
100
 
101
101
  ```bash
102
- hotfun create-token <name> <symbol> <uri>
102
+ hotfun create-token <name> <symbol> <image_url> [--description "desc"] [--x-royalty-party "username"]
103
103
  ```
104
104
 
105
105
  Under the hood, the single command handles the full flow:
106
106
 
107
- 1. **Call API** — POST to `https://gate.game.com/v3/hotfun/create_pool_with_config` with payer (from PRIVATE_KEY), name, symbol, uri. API returns a base58-encoded Solana transaction.
107
+ 1. **Call API** — POST (multipart/form-data) to `https://gate.game.com/v3/hotfun/agent/create_pool_with_config` with payer (from PRIVATE_KEY), name, symbol, image_url, agent_ts (current Unix timestamp), agent_sign (Ed25519 signature of agent_ts with private key, base58 encoded), and optional description / x_royalty_party. API returns a base58-encoded Solana transaction.
108
108
  2. **Sign** — Deserialize the transaction and sign it with the wallet private key.
109
109
  3. **Send** — Send the signed transaction to Solana RPC and confirm.
110
110
 
111
- Output JSON includes: `txHash`, `wallet`, `baseMint` (new token address), `dbcConfig`, `dbcPool`, `name`, `symbol`, `uri`.
111
+ Output JSON includes: `txHash`, `wallet`, `baseMint` (new token address), `dbcConfig`, `dbcPool`, `name`, `symbol`, `imageUrl`, `uri`.
112
112
 
113
113
  Full API and parameters: [references/api-create-token.md](references/api-create-token.md).
114
114
 
@@ -116,7 +116,7 @@ Full API and parameters: [references/api-create-token.md](references/api-create-
116
116
 
117
117
  | Need | Command | When |
118
118
  |------|---------|------|
119
- | **Create token** | `hotfun create-token <name> <symbol> <uri>` | API → sign → send to Solana. Env: PRIVATE_KEY. |
119
+ | **Create token** | `hotfun create-token <name> <symbol> <image_url> [options]` | API → sign → send to Solana. Env: PRIVATE_KEY. |
120
120
 
121
121
  Chain: **Solana only**.
122
122
 
@@ -139,7 +139,6 @@ Set **PRIVATE_KEY** and optionally **SOLANA_RPC_URL** via the process environmen
139
139
 
140
140
  - **PRIVATE_KEY** (required for write operations): Solana wallet private key in base58 or JSON array format.
141
141
  - **SOLANA_RPC_URL** (optional): Solana RPC endpoint; if unset, scripts use `https://api.mainnet-beta.solana.com`.
142
- - **ROYALTY_PARTY** (optional): Royalty recipient Solana address; defaults to `11111111111111111111111111111111` (system program = no royalty).
143
142
 
144
143
  ### Execution and install
145
144
 
@@ -12,25 +12,22 @@ The `hotfun create-token` command handles the full flow in a single invocation:
12
12
 
13
13
  | Method | Endpoint |
14
14
  |--------|----------|
15
- | POST | `https://gate.game.com/v3/hotfun/create_pool_with_config` |
15
+ | POST | `https://gate.game.com/v3/hotfun/agent/create_pool_with_config` |
16
16
 
17
17
  ### Request
18
18
 
19
- **Content-Type**: `application/x-www-form-urlencoded`
19
+ **Content-Type**: `multipart/form-data`
20
20
 
21
21
  | Parameter | Required | Description |
22
22
  |-----------|----------|-------------|
23
23
  | `payer` | Yes | Solana wallet public key (derived from PRIVATE_KEY) |
24
- | `royalty_party` | Yes | Royalty recipient address. Default: `11111111111111111111111111111111` (system program = no royalty) |
25
- | `name` | Yes | Token name |
26
- | `symbol` | Yes | Token symbol |
27
- | `uri` | Yes | Token metadata / image URI (e.g. IPFS link) |
28
-
29
- **Required headers**:
30
- ```
31
- Origin: https://hot.fun
32
- Referer: https://hot.fun/
33
- ```
24
+ | `name` | Yes | Token name, max 255 chars |
25
+ | `symbol` | Yes | Token symbol, max 32 chars |
26
+ | `image_url` | Yes | Token image URL (e.g. IPFS link) |
27
+ | `agent_ts` | Yes | Current Unix timestamp (valid for 5 minutes) |
28
+ | `agent_sign` | Yes | Ed25519 signature of `agent_ts` using wallet private key, base58 encoded |
29
+ | `description` | No | Token description |
30
+ | `x_royalty_party` | No | X (Twitter) username for royalty party |
34
31
 
35
32
  ### Response
36
33
 
@@ -41,10 +38,14 @@ Referer: https://hot.fun/
41
38
  "signature": "",
42
39
  "dbc_config": "<pubkey>",
43
40
  "dbc_pool": "<pubkey>",
44
- "base_mint": "<pubkey>"
41
+ "base_mint": "<pubkey>",
42
+ "name": "MyToken",
43
+ "symbol": "MTK",
44
+ "uri": "<metadata URI>",
45
+ "royalty_party": "<pubkey>"
45
46
  },
46
47
  "common": {
47
- "timestamp": 1772694804,
48
+ "timestamp": 1772948092,
48
49
  "app_name": "hotfunv3",
49
50
  "chain_asset_config": [...],
50
51
  "config": { "check_chinese_symbol_duplicate": false }
@@ -60,17 +61,33 @@ Referer: https://hot.fun/
60
61
  | `base_mint` | The new token's mint address |
61
62
  | `dbc_config` | DBC config account |
62
63
  | `dbc_pool` | DBC pool account |
64
+ | `uri` | Token metadata URI (generated by API) |
65
+ | `royalty_party` | Royalty party account |
63
66
 
64
67
  ### Example
65
68
 
66
69
  ```bash
67
- curl 'https://gate.game.com/v3/hotfun/create_pool_with_config' \
68
- -H 'Content-Type: application/x-www-form-urlencoded' \
69
- -H 'Origin: https://hot.fun' \
70
- -H 'Referer: https://hot.fun/' \
71
- --data-raw 'payer=<WALLET_PUBKEY>&royalty_party=11111111111111111111111111111111&name=MyToken&symbol=MTK&uri=https%3A%2F%2Fexample.com%2Fmetadata.json'
70
+ curl --request POST \
71
+ --url https://gate.game.com/v3/hotfun/agent/create_pool_with_config \
72
+ --header 'content-type: multipart/form-data' \
73
+ --form payer=<WALLET_PUBKEY> \
74
+ --form 'name=MyToken' \
75
+ --form 'symbol=MTK' \
76
+ --form image_url=https://example.com/image.png \
77
+ --form 'description=A cool token' \
78
+ --form x_royalty_party=myTwitter \
79
+ --form agent_ts=1772947790 \
80
+ --form agent_sign=<BASE58_ED25519_SIGNATURE>
72
81
  ```
73
82
 
83
+ ## Authentication (agent_ts + agent_sign)
84
+
85
+ To prevent abuse, the API requires request authentication:
86
+
87
+ 1. Generate `agent_ts`: current Unix timestamp in seconds.
88
+ 2. Generate `agent_sign`: sign the `agent_ts` string using the wallet's Ed25519 private key, then base58-encode the 64-byte signature.
89
+ 3. The timestamp is valid for 5 minutes from generation.
90
+
74
91
  ## Sign and Send
75
92
 
76
93
  1. Decode the base58 `transaction` string to bytes.
@@ -7,10 +7,14 @@
7
7
  * 3. Send the signed transaction to Solana RPC
8
8
  *
9
9
  * Usage:
10
- * npx hotfun create-token <name> <symbol> <uri>
10
+ * npx hotfun create-token <name> <symbol> <image_url> [options]
11
+ *
12
+ * Options:
13
+ * --description <desc> Token description
14
+ * --x-royalty-party <user> X (Twitter) username for royalty party
11
15
  *
12
16
  * Env: PRIVATE_KEY (Solana wallet private key, base58 or JSON array)
13
- * Optional env: SOLANA_RPC_URL, ROYALTY_PARTY
17
+ * Optional env: SOLANA_RPC_URL
14
18
  */
15
19
 
16
20
  import {
@@ -19,9 +23,9 @@ import {
19
23
  VersionedTransaction,
20
24
  } from '@solana/web3.js';
21
25
  import bs58 from 'bs58';
26
+ import nacl from 'tweetnacl';
22
27
 
23
- const API_URL = 'https://gate.game.com/v3/hotfun/create_pool_with_config';
24
- const DEFAULT_ROYALTY_PARTY = '11111111111111111111111111111111';
28
+ const API_URL = 'https://gate.game.com/v3/hotfun/agent/create_pool_with_config';
25
29
 
26
30
  function loadKeypair(privateKey: string): Keypair {
27
31
  try {
@@ -35,14 +39,36 @@ function loadKeypair(privateKey: string): Keypair {
35
39
  }
36
40
  }
37
41
 
38
- async function main() {
39
- const name = process.argv[2];
40
- const symbol = process.argv[3];
41
- const uri = process.argv[4];
42
+ function parseArgs(argv: string[]) {
43
+ const positional: string[] = [];
44
+ const options: Record<string, string> = {};
45
+ let i = 0;
46
+ while (i < argv.length) {
47
+ if (argv[i] === '--description' && i + 1 < argv.length) {
48
+ options.description = argv[++i];
49
+ } else if (argv[i] === '--x-royalty-party' && i + 1 < argv.length) {
50
+ options.xRoyaltyParty = argv[++i];
51
+ } else if (!argv[i].startsWith('--')) {
52
+ positional.push(argv[i]);
53
+ }
54
+ i++;
55
+ }
56
+ return { positional, options };
57
+ }
42
58
 
43
- if (!name || !symbol || !uri) {
44
- console.error('Usage: npx hotfun create-token <name> <symbol> <uri>');
45
- console.error('Example: npx hotfun create-token MyToken MTK "https://example.com/metadata.json"');
59
+ async function main() {
60
+ const { positional, options } = parseArgs(process.argv.slice(2));
61
+ const name = positional[0];
62
+ const symbol = positional[1];
63
+ const imageUrl = positional[2];
64
+
65
+ if (!name || !symbol || !imageUrl) {
66
+ console.error('Usage: npx hotfun create-token <name> <symbol> <image_url> [options]');
67
+ console.error('Options:');
68
+ console.error(' --description <desc> Token description');
69
+ console.error(' --x-royalty-party <user> X (Twitter) username for royalty party');
70
+ console.error('');
71
+ console.error('Example: npx hotfun create-token MyToken MTK "https://example.com/image.png" --description "A cool token" --x-royalty-party "myTwitter"');
46
72
  process.exit(1);
47
73
  }
48
74
 
@@ -54,35 +80,40 @@ async function main() {
54
80
 
55
81
  const keypair = loadKeypair(privateKey);
56
82
  const payer = keypair.publicKey.toBase58();
57
- const royaltyParty = process.env.ROYALTY_PARTY || DEFAULT_ROYALTY_PARTY;
58
83
  const rpcUrl = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
59
84
  const connection = new Connection(rpcUrl, 'confirmed');
60
85
 
86
+ // ── Generate agent_ts and agent_sign ──────────────────────────────────
87
+ const agentTs = Math.floor(Date.now() / 1000).toString();
88
+ const agentTsBytes = new TextEncoder().encode(agentTs);
89
+ const signature = nacl.sign.detached(agentTsBytes, keypair.secretKey);
90
+ const agentSign = bs58.encode(signature);
91
+
61
92
  // ── Step 1: Call API to get transaction ────────────────────────────────
62
93
  console.error(`Creating token "${name}" (${symbol}) ...`);
63
94
  console.error(` payer: ${payer}`);
64
- console.error(` uri: ${uri}`);
65
-
66
- const body = new URLSearchParams({
67
- payer,
68
- royalty_party: royaltyParty,
69
- name,
70
- symbol,
71
- uri,
72
- });
95
+ console.error(` image_url: ${imageUrl}`);
96
+ if (options.description) console.error(` description: ${options.description}`);
97
+ if (options.xRoyaltyParty) console.error(` x_royalty_party: ${options.xRoyaltyParty}`);
98
+
99
+ const formData = new FormData();
100
+ formData.append('payer', payer);
101
+ formData.append('name', name);
102
+ formData.append('symbol', symbol);
103
+ formData.append('image_url', imageUrl);
104
+ formData.append('agent_ts', agentTs);
105
+ formData.append('agent_sign', agentSign);
106
+ if (options.description) formData.append('description', options.description);
107
+ if (options.xRoyaltyParty) formData.append('x_royalty_party', options.xRoyaltyParty);
73
108
 
74
109
  const res = await fetch(API_URL, {
75
110
  method: 'POST',
76
- headers: {
77
- 'Content-Type': 'application/x-www-form-urlencoded',
78
- 'Origin': 'https://hot.fun',
79
- 'Referer': 'https://hot.fun/',
80
- },
81
- body: body.toString(),
111
+ body: formData,
82
112
  });
83
113
 
84
114
  if (!res.ok) {
85
- throw new Error(`API request failed: ${res.status} ${res.statusText}`);
115
+ const text = await res.text().catch(() => '');
116
+ throw new Error(`API request failed: ${res.status} ${res.statusText}\n${text}`);
86
117
  }
87
118
 
88
119
  const json = await res.json() as {
@@ -92,11 +123,15 @@ async function main() {
92
123
  dbc_config: string;
93
124
  dbc_pool: string;
94
125
  base_mint: string;
126
+ name: string;
127
+ symbol: string;
128
+ uri: string;
129
+ royalty_party: string;
95
130
  };
96
131
  common: Record<string, unknown>;
97
132
  };
98
133
 
99
- const { transaction: txBase58, dbc_config, dbc_pool, base_mint } = json.data;
134
+ const { transaction: txBase58, dbc_config, dbc_pool, base_mint, uri } = json.data;
100
135
 
101
136
  if (!txBase58) {
102
137
  throw new Error('API returned empty transaction. Response: ' + JSON.stringify(json));
@@ -105,6 +140,7 @@ async function main() {
105
140
  console.error(` base_mint: ${base_mint}`);
106
141
  console.error(` dbc_config: ${dbc_config}`);
107
142
  console.error(` dbc_pool: ${dbc_pool}`);
143
+ console.error(` uri: ${uri}`);
108
144
 
109
145
  // ── Step 2: Deserialize and sign transaction ──────────────────────────
110
146
  const txBytes = bs58.decode(txBase58);
@@ -134,6 +170,7 @@ async function main() {
134
170
  dbcPool: dbc_pool,
135
171
  name,
136
172
  symbol,
173
+ imageUrl,
137
174
  uri,
138
175
  };
139
176
  console.log(JSON.stringify(out, null, 2));