clawdpad 1.0.0
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/README.md +125 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +464 -0
- package/package.json +48 -0
- package/src/index.ts +509 -0
- package/tsconfig.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Clawdpad CLI
|
|
2
|
+
|
|
3
|
+
> One-command token launch for AI agents 🦞
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx clawdpad launch "Doge AI" "DOGEAI" --supply 1000000000
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Use directly with npx (no install needed)
|
|
13
|
+
npx clawdpad launch "MyToken" "MTK"
|
|
14
|
+
|
|
15
|
+
# Or install globally
|
|
16
|
+
npm install -g clawdpad
|
|
17
|
+
clawdpad launch "MyToken" "MTK"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
### Launch a Token
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Basic launch
|
|
26
|
+
clawdpad launch "Token Name" "SYMBOL"
|
|
27
|
+
|
|
28
|
+
# With options
|
|
29
|
+
clawdpad launch "Doge AI" "DOGEAI" \
|
|
30
|
+
--supply 1000000000 \
|
|
31
|
+
--description "The smartest doge on Base" \
|
|
32
|
+
--website "https://dogeai.xyz" \
|
|
33
|
+
--twitter "@dogeai" \
|
|
34
|
+
--wallet 0xYourAddress
|
|
35
|
+
|
|
36
|
+
# Dry run (test without launching)
|
|
37
|
+
clawdpad launch "Test Token" "TEST" --dry-run
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### List Recent Tokens
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
clawdpad list
|
|
44
|
+
clawdpad list --limit 20
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Check Trust Score
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
clawdpad trust 0xYourWalletAddress
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Interactive Mode
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
clawdpad interactive
|
|
57
|
+
# or
|
|
58
|
+
clawdpad i
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Get Info
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
clawdpad info
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Options
|
|
68
|
+
|
|
69
|
+
| Option | Description |
|
|
70
|
+
|--------|-------------|
|
|
71
|
+
| `-s, --supply <amount>` | Total supply (default: 1,000,000,000) |
|
|
72
|
+
| `-d, --description <text>` | Token description |
|
|
73
|
+
| `-w, --website <url>` | Website URL |
|
|
74
|
+
| `-t, --twitter <handle>` | Twitter/X handle |
|
|
75
|
+
| `-i, --image <url>` | Token image URL |
|
|
76
|
+
| `--wallet <address>` | Creator wallet (receives tokens) |
|
|
77
|
+
| `--dry-run` | Simulate without launching |
|
|
78
|
+
|
|
79
|
+
## Payment
|
|
80
|
+
|
|
81
|
+
Launching a token costs **$1 USDC** via x402 protocol.
|
|
82
|
+
|
|
83
|
+
When you run the launch command:
|
|
84
|
+
1. You'll receive payment instructions
|
|
85
|
+
2. Send $1 USDC to the treasury on Base
|
|
86
|
+
3. Re-run the command with payment proof
|
|
87
|
+
|
|
88
|
+
## For AI Agents
|
|
89
|
+
|
|
90
|
+
Example integration:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
import { exec } from 'child_process';
|
|
94
|
+
|
|
95
|
+
async function launchToken(name, symbol, wallet) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
exec(
|
|
98
|
+
`npx clawdpad launch "${name}" "${symbol}" --wallet ${wallet}`,
|
|
99
|
+
(error, stdout, stderr) => {
|
|
100
|
+
if (error) reject(error);
|
|
101
|
+
else resolve(stdout);
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Usage
|
|
108
|
+
await launchToken("My AI Token", "MYAI", "0x...");
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Environment Variables
|
|
112
|
+
|
|
113
|
+
| Variable | Description |
|
|
114
|
+
|----------|-------------|
|
|
115
|
+
| `CLAWDPAD_API_URL` | Custom API URL (default: https://www.clawdpad.ai/api) |
|
|
116
|
+
|
|
117
|
+
## Links
|
|
118
|
+
|
|
119
|
+
- Website: https://www.clawdpad.ai
|
|
120
|
+
- Token: $CLAWDPAD on Base
|
|
121
|
+
- Twitter: @clawdpad
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const ora_1 = __importDefault(require("ora"));
|
|
43
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const viem_1 = require("viem");
|
|
48
|
+
const chains_1 = require("viem/chains");
|
|
49
|
+
const accounts_1 = require("viem/accounts");
|
|
50
|
+
const API_URL = process.env.CLAWDPAD_API_URL || 'https://www.clawdpad.ai/api';
|
|
51
|
+
const CONFIG_DIR = path.join(os.homedir(), '.clawdpad');
|
|
52
|
+
const WALLET_FILE = path.join(CONFIG_DIR, 'wallet.json');
|
|
53
|
+
const LAUNCHES_FILE = path.join(CONFIG_DIR, 'launches.json');
|
|
54
|
+
// Clanker API (for token deployment)
|
|
55
|
+
const CLANKER_API = 'https://www.clanker.world/api/v1';
|
|
56
|
+
const program = new commander_1.Command();
|
|
57
|
+
// ASCII Art Banner
|
|
58
|
+
const banner = `
|
|
59
|
+
${chalk_1.default.cyan(' ██████╗██╗ █████╗ ██╗ ██╗██████╗ ██████╗ █████╗ ██████╗ ')}
|
|
60
|
+
${chalk_1.default.cyan(' ██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔══██╗██╔══██╗')}
|
|
61
|
+
${chalk_1.default.cyan(' ██║ ██║ ███████║██║ █╗ ██║██║ ██║██████╔╝███████║██║ ██║')}
|
|
62
|
+
${chalk_1.default.cyan(' ██║ ██║ ██╔══██║██║███╗██║██║ ██║██╔═══╝ ██╔══██║██║ ██║')}
|
|
63
|
+
${chalk_1.default.cyan(' ╚██████╗███████╗██║ ██║╚███╔███╔╝██████╔╝██║ ██║ ██║██████╔╝')}
|
|
64
|
+
${chalk_1.default.cyan(' ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ')}
|
|
65
|
+
${chalk_1.default.gray(' one command. your token. live on base.')}
|
|
66
|
+
`;
|
|
67
|
+
function ensureConfigDir() {
|
|
68
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
69
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function loadWallet() {
|
|
73
|
+
if (!fs.existsSync(WALLET_FILE)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const data = fs.readFileSync(WALLET_FILE, 'utf-8');
|
|
78
|
+
return JSON.parse(data);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function createWallet() {
|
|
85
|
+
ensureConfigDir();
|
|
86
|
+
const privateKey = (0, accounts_1.generatePrivateKey)();
|
|
87
|
+
const account = (0, accounts_1.privateKeyToAccount)(privateKey);
|
|
88
|
+
const wallet = {
|
|
89
|
+
address: account.address,
|
|
90
|
+
privateKey: privateKey,
|
|
91
|
+
createdAt: new Date().toISOString(),
|
|
92
|
+
};
|
|
93
|
+
fs.writeFileSync(WALLET_FILE, JSON.stringify(wallet, null, 2), { mode: 0o600 });
|
|
94
|
+
return wallet;
|
|
95
|
+
}
|
|
96
|
+
function getOrCreateWallet() {
|
|
97
|
+
const existing = loadWallet();
|
|
98
|
+
if (existing) {
|
|
99
|
+
return { wallet: existing, isNew: false };
|
|
100
|
+
}
|
|
101
|
+
return { wallet: createWallet(), isNew: true };
|
|
102
|
+
}
|
|
103
|
+
function loadLaunches() {
|
|
104
|
+
if (!fs.existsSync(LAUNCHES_FILE)) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const data = fs.readFileSync(LAUNCHES_FILE, 'utf-8');
|
|
109
|
+
return JSON.parse(data);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function saveLaunch(launch) {
|
|
116
|
+
ensureConfigDir();
|
|
117
|
+
const launches = loadLaunches();
|
|
118
|
+
launches.push(launch);
|
|
119
|
+
fs.writeFileSync(LAUNCHES_FILE, JSON.stringify(launches, null, 2));
|
|
120
|
+
}
|
|
121
|
+
// ============ Image Upload ============
|
|
122
|
+
async function uploadImage(imagePath) {
|
|
123
|
+
// Read image file
|
|
124
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
125
|
+
const base64 = imageBuffer.toString('base64');
|
|
126
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
127
|
+
const mimeType = ext === '.png' ? 'image/png' :
|
|
128
|
+
ext === '.gif' ? 'image/gif' :
|
|
129
|
+
ext === '.webp' ? 'image/webp' : 'image/jpeg';
|
|
130
|
+
// Upload to Clawdpad (which uses imgbb or similar)
|
|
131
|
+
const response = await (0, node_fetch_1.default)(`${API_URL}/upload`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: { 'Content-Type': 'application/json' },
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
image: `data:${mimeType};base64,${base64}`,
|
|
136
|
+
name: path.basename(imagePath, ext),
|
|
137
|
+
}),
|
|
138
|
+
});
|
|
139
|
+
const result = await response.json();
|
|
140
|
+
if (!result.success || !result.url) {
|
|
141
|
+
throw new Error(result.error || 'Image upload failed');
|
|
142
|
+
}
|
|
143
|
+
return result.url;
|
|
144
|
+
}
|
|
145
|
+
// ============ Token Launch via Clanker ============
|
|
146
|
+
async function launchViaClanker(params) {
|
|
147
|
+
// For now, use Clawdpad API which proxies to Clanker
|
|
148
|
+
// This allows us to sponsor gas and handle the complexity
|
|
149
|
+
const response = await (0, node_fetch_1.default)(`${API_URL}/launch`, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: { 'Content-Type': 'application/json' },
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
name: params.name,
|
|
154
|
+
symbol: params.symbol,
|
|
155
|
+
description: params.description,
|
|
156
|
+
image: params.image,
|
|
157
|
+
website: params.website,
|
|
158
|
+
creatorWallet: params.wallet.address,
|
|
159
|
+
testnet: params.testnet,
|
|
160
|
+
}),
|
|
161
|
+
});
|
|
162
|
+
const result = await response.json();
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
throw new Error(result.error || 'Launch failed');
|
|
165
|
+
}
|
|
166
|
+
const chainName = params.testnet ? 'base-sepolia' : 'base';
|
|
167
|
+
const explorerBase = params.testnet ? 'https://sepolia.basescan.org' : 'https://basescan.org';
|
|
168
|
+
return {
|
|
169
|
+
tokenAddress: result.token?.address || result.tokenAddress || '0x...',
|
|
170
|
+
transactionHash: result.transactionHash || result.tx_hash || '0x...',
|
|
171
|
+
clankerUrl: `https://clanker.world/clanker/${result.token?.address || result.tokenAddress}`,
|
|
172
|
+
explorerUrl: `${explorerBase}/token/${result.token?.address || result.tokenAddress}`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// ============ CLI Commands ============
|
|
176
|
+
program
|
|
177
|
+
.name('clawdpad')
|
|
178
|
+
.description('Launch memecoins on Base — one command, no gas, earn fees')
|
|
179
|
+
.version('1.0.0');
|
|
180
|
+
// Main launch command
|
|
181
|
+
program
|
|
182
|
+
.option('--name <name>', 'Token name')
|
|
183
|
+
.option('--symbol <symbol>', 'Token symbol')
|
|
184
|
+
.option('--description <text>', 'Token description')
|
|
185
|
+
.option('--image <path>', 'Path to image (PNG/JPG/GIF/WebP, max 5MB)')
|
|
186
|
+
.option('--website <url>', 'Website URL (e.g., Moltbook post)')
|
|
187
|
+
.option('--testnet', 'Use Base Sepolia testnet')
|
|
188
|
+
.option('--json', 'Output JSON (machine-readable)')
|
|
189
|
+
.action(async (options) => {
|
|
190
|
+
const jsonMode = options.json;
|
|
191
|
+
if (!jsonMode) {
|
|
192
|
+
console.log(banner);
|
|
193
|
+
}
|
|
194
|
+
// Check if this is a subcommand or launch
|
|
195
|
+
if (!options.name && !options.symbol) {
|
|
196
|
+
if (!jsonMode) {
|
|
197
|
+
program.help();
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Validate required fields
|
|
202
|
+
if (!options.name || !options.symbol) {
|
|
203
|
+
const error = { success: false, error: 'Missing required fields: --name and --symbol' };
|
|
204
|
+
if (jsonMode) {
|
|
205
|
+
console.log(JSON.stringify(error));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
console.error(chalk_1.default.red('Error: --name and --symbol are required'));
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
const spinner = jsonMode ? null : (0, ora_1.default)('Preparing launch...').start();
|
|
212
|
+
try {
|
|
213
|
+
// Get or create wallet
|
|
214
|
+
const { wallet, isNew } = getOrCreateWallet();
|
|
215
|
+
if (spinner) {
|
|
216
|
+
spinner.text = isNew ? 'Created new wallet...' : 'Using existing wallet...';
|
|
217
|
+
}
|
|
218
|
+
// Upload image if provided
|
|
219
|
+
let imageUrl = '';
|
|
220
|
+
if (options.image) {
|
|
221
|
+
if (spinner)
|
|
222
|
+
spinner.text = 'Uploading image...';
|
|
223
|
+
if (options.image.startsWith('http')) {
|
|
224
|
+
imageUrl = options.image;
|
|
225
|
+
}
|
|
226
|
+
else if (fs.existsSync(options.image)) {
|
|
227
|
+
imageUrl = await uploadImage(options.image);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
throw new Error(`Image not found: ${options.image}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Launch token
|
|
234
|
+
if (spinner)
|
|
235
|
+
spinner.text = 'Launching token on Base...';
|
|
236
|
+
const result = await launchViaClanker({
|
|
237
|
+
name: options.name,
|
|
238
|
+
symbol: options.symbol,
|
|
239
|
+
description: options.description || `${options.name} - Launched via Clawdpad`,
|
|
240
|
+
image: imageUrl,
|
|
241
|
+
website: options.website,
|
|
242
|
+
wallet,
|
|
243
|
+
testnet: options.testnet,
|
|
244
|
+
});
|
|
245
|
+
// Save launch record
|
|
246
|
+
saveLaunch({
|
|
247
|
+
tokenAddress: result.tokenAddress,
|
|
248
|
+
name: options.name,
|
|
249
|
+
symbol: options.symbol,
|
|
250
|
+
network: options.testnet ? 'base-sepolia' : 'base',
|
|
251
|
+
transactionHash: result.transactionHash,
|
|
252
|
+
launchedAt: new Date().toISOString(),
|
|
253
|
+
website: options.website,
|
|
254
|
+
});
|
|
255
|
+
if (spinner)
|
|
256
|
+
spinner.succeed('Token launched!');
|
|
257
|
+
// Output
|
|
258
|
+
const output = {
|
|
259
|
+
success: true,
|
|
260
|
+
tokenAddress: result.tokenAddress,
|
|
261
|
+
transactionHash: result.transactionHash,
|
|
262
|
+
name: options.name,
|
|
263
|
+
symbol: options.symbol,
|
|
264
|
+
network: options.testnet ? 'Base Sepolia' : 'Base',
|
|
265
|
+
explorer: result.explorerUrl,
|
|
266
|
+
clanker: result.clankerUrl,
|
|
267
|
+
wallet: wallet.address,
|
|
268
|
+
...(isNew ? { privateKey: wallet.privateKey } : {}),
|
|
269
|
+
};
|
|
270
|
+
if (jsonMode) {
|
|
271
|
+
console.log(JSON.stringify(output, null, 2));
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
console.log('\n' + chalk_1.default.green('✓ Token launched successfully!'));
|
|
275
|
+
console.log(chalk_1.default.gray('─'.repeat(60)));
|
|
276
|
+
console.log(` ${chalk_1.default.gray('Name:')} ${chalk_1.default.white(options.name)}`);
|
|
277
|
+
console.log(` ${chalk_1.default.gray('Symbol:')} ${chalk_1.default.cyan(options.symbol)}`);
|
|
278
|
+
console.log(` ${chalk_1.default.gray('Network:')} ${chalk_1.default.blue(options.testnet ? 'Base Sepolia' : 'Base')}`);
|
|
279
|
+
console.log(` ${chalk_1.default.gray('Address:')} ${chalk_1.default.yellow(result.tokenAddress)}`);
|
|
280
|
+
console.log(` ${chalk_1.default.gray('Wallet:')} ${chalk_1.default.gray(wallet.address)}`);
|
|
281
|
+
console.log(chalk_1.default.gray('─'.repeat(60)));
|
|
282
|
+
console.log(`\n ${chalk_1.default.white('View on Basescan:')}`);
|
|
283
|
+
console.log(` ${chalk_1.default.cyan(result.explorerUrl)}`);
|
|
284
|
+
console.log(`\n ${chalk_1.default.white('View on Clanker:')}`);
|
|
285
|
+
console.log(` ${chalk_1.default.cyan(result.clankerUrl)}`);
|
|
286
|
+
if (isNew) {
|
|
287
|
+
console.log('\n' + chalk_1.default.yellow('⚠️ NEW WALLET CREATED'));
|
|
288
|
+
console.log(chalk_1.default.gray(' Save this private key — it won\'t be shown again:'));
|
|
289
|
+
console.log(` ${chalk_1.default.red(wallet.privateKey)}`);
|
|
290
|
+
}
|
|
291
|
+
console.log('\n' + chalk_1.default.gray('Earn 80% of all trading fees! Check with: clawdpad fees'));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
if (spinner)
|
|
296
|
+
spinner.fail('Launch failed');
|
|
297
|
+
const errorOutput = { success: false, error: error.message };
|
|
298
|
+
if (jsonMode) {
|
|
299
|
+
console.log(JSON.stringify(errorOutput));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
console.error(chalk_1.default.red(`\nError: ${error.message}`));
|
|
303
|
+
}
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
// Wallet command
|
|
308
|
+
program
|
|
309
|
+
.command('wallet')
|
|
310
|
+
.description('Show wallet address and balance')
|
|
311
|
+
.option('--json', 'Output JSON')
|
|
312
|
+
.action(async (options) => {
|
|
313
|
+
const { wallet, isNew } = getOrCreateWallet();
|
|
314
|
+
// Get balance
|
|
315
|
+
const client = (0, viem_1.createPublicClient)({
|
|
316
|
+
chain: chains_1.base,
|
|
317
|
+
transport: (0, viem_1.http)(),
|
|
318
|
+
});
|
|
319
|
+
const balance = await client.getBalance({ address: wallet.address });
|
|
320
|
+
const output = {
|
|
321
|
+
address: wallet.address,
|
|
322
|
+
balance: (0, viem_1.formatEther)(balance),
|
|
323
|
+
balanceWei: balance.toString(),
|
|
324
|
+
isNew,
|
|
325
|
+
};
|
|
326
|
+
if (options.json) {
|
|
327
|
+
console.log(JSON.stringify(output, null, 2));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
console.log(banner);
|
|
331
|
+
console.log(chalk_1.default.white('Wallet:'));
|
|
332
|
+
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
333
|
+
console.log(` Address: ${chalk_1.default.cyan(wallet.address)}`);
|
|
334
|
+
console.log(` Balance: ${chalk_1.default.green((0, viem_1.formatEther)(balance))} ETH`);
|
|
335
|
+
if (isNew) {
|
|
336
|
+
console.log(chalk_1.default.yellow('\n ⚠️ New wallet created'));
|
|
337
|
+
console.log(chalk_1.default.gray(` Private key saved to: ${WALLET_FILE}`));
|
|
338
|
+
}
|
|
339
|
+
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
// Status command
|
|
343
|
+
program
|
|
344
|
+
.command('status')
|
|
345
|
+
.description('List all launched tokens')
|
|
346
|
+
.option('--json', 'Output JSON')
|
|
347
|
+
.action(async (options) => {
|
|
348
|
+
const launches = loadLaunches();
|
|
349
|
+
if (options.json) {
|
|
350
|
+
console.log(JSON.stringify({ launches }, null, 2));
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
console.log(banner);
|
|
354
|
+
console.log(chalk_1.default.white(`Launched Tokens (${launches.length}):`));
|
|
355
|
+
console.log(chalk_1.default.gray('─'.repeat(70)));
|
|
356
|
+
if (launches.length === 0) {
|
|
357
|
+
console.log(chalk_1.default.gray(' No tokens launched yet. Run: clawdpad --name "MyToken" --symbol "TKN"'));
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
for (const launch of launches) {
|
|
361
|
+
console.log(` ${chalk_1.default.cyan(launch.symbol.padEnd(10))} ` +
|
|
362
|
+
`${chalk_1.default.white(launch.name.slice(0, 20).padEnd(22))} ` +
|
|
363
|
+
`${chalk_1.default.gray(launch.tokenAddress.slice(0, 16) + '...')}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
console.log(chalk_1.default.gray('─'.repeat(70)));
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
// Fees command
|
|
370
|
+
program
|
|
371
|
+
.command('fees')
|
|
372
|
+
.description('Check claimable fees')
|
|
373
|
+
.option('--json', 'Output JSON')
|
|
374
|
+
.action(async (options) => {
|
|
375
|
+
const wallet = loadWallet();
|
|
376
|
+
if (!wallet) {
|
|
377
|
+
const error = { success: false, error: 'No wallet found. Launch a token first.' };
|
|
378
|
+
if (options.json) {
|
|
379
|
+
console.log(JSON.stringify(error));
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
console.error(chalk_1.default.red('No wallet found. Launch a token first.'));
|
|
383
|
+
}
|
|
384
|
+
process.exit(2);
|
|
385
|
+
}
|
|
386
|
+
// Check fees via Clanker FeeLocker
|
|
387
|
+
// For now, return placeholder
|
|
388
|
+
const output = {
|
|
389
|
+
wallet: wallet.address,
|
|
390
|
+
claimableWETH: '0',
|
|
391
|
+
claimableTokens: '0',
|
|
392
|
+
canClaim: false,
|
|
393
|
+
hasGas: false,
|
|
394
|
+
hint: 'Fees accumulate when people trade your tokens',
|
|
395
|
+
};
|
|
396
|
+
if (options.json) {
|
|
397
|
+
console.log(JSON.stringify(output, null, 2));
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
console.log(banner);
|
|
401
|
+
console.log(chalk_1.default.white('Claimable Fees:'));
|
|
402
|
+
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
403
|
+
console.log(` Wallet: ${chalk_1.default.gray(wallet.address)}`);
|
|
404
|
+
console.log(` WETH Fees: ${chalk_1.default.green(output.claimableWETH)} WETH`);
|
|
405
|
+
console.log(` Can Claim: ${output.canClaim ? chalk_1.default.green('Yes') : chalk_1.default.yellow('No')}`);
|
|
406
|
+
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
407
|
+
console.log(chalk_1.default.gray('\n Fees accumulate when people trade your tokens.'));
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
// Claim command
|
|
411
|
+
program
|
|
412
|
+
.command('claim')
|
|
413
|
+
.description('Withdraw accumulated fees')
|
|
414
|
+
.option('--json', 'Output JSON')
|
|
415
|
+
.action(async (options) => {
|
|
416
|
+
const wallet = loadWallet();
|
|
417
|
+
if (!wallet) {
|
|
418
|
+
const error = { success: false, error: 'No wallet found' };
|
|
419
|
+
if (options.json) {
|
|
420
|
+
console.log(JSON.stringify(error));
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
console.error(chalk_1.default.red('No wallet found. Launch a token first.'));
|
|
424
|
+
}
|
|
425
|
+
process.exit(2);
|
|
426
|
+
}
|
|
427
|
+
// TODO: Implement actual fee claiming via Clanker FeeLocker contract
|
|
428
|
+
const output = {
|
|
429
|
+
success: false,
|
|
430
|
+
error: 'No fees to claim yet. Keep promoting your tokens!',
|
|
431
|
+
};
|
|
432
|
+
if (options.json) {
|
|
433
|
+
console.log(JSON.stringify(output));
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
console.log(banner);
|
|
437
|
+
console.log(chalk_1.default.yellow('No fees to claim yet.'));
|
|
438
|
+
console.log(chalk_1.default.gray('Keep promoting your tokens! Fees accumulate from trades.'));
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
// Info command
|
|
442
|
+
program
|
|
443
|
+
.command('info')
|
|
444
|
+
.description('About Clawdpad')
|
|
445
|
+
.action(() => {
|
|
446
|
+
console.log(banner);
|
|
447
|
+
console.log(chalk_1.default.white('\nWhat is Clawdpad?'));
|
|
448
|
+
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
449
|
+
console.log(' Clawdpad lets AI agents launch tokens on Base');
|
|
450
|
+
console.log(' with one command. No wallet setup, no gas fees.');
|
|
451
|
+
console.log('');
|
|
452
|
+
console.log(chalk_1.default.white('How it works:'));
|
|
453
|
+
console.log(' 1. Run: clawdpad --name "X" --symbol "X"');
|
|
454
|
+
console.log(' 2. Token deploys on Base via Clanker');
|
|
455
|
+
console.log(' 3. Earn 80% of all trading fees');
|
|
456
|
+
console.log(' 4. Claim fees anytime: clawdpad claim');
|
|
457
|
+
console.log('');
|
|
458
|
+
console.log(chalk_1.default.white('Links:'));
|
|
459
|
+
console.log(` Website: ${chalk_1.default.cyan('https://www.clawdpad.ai')}`);
|
|
460
|
+
console.log(` Token: ${chalk_1.default.cyan('$CLAWDPAD on Base')}`);
|
|
461
|
+
console.log(` Twitter: ${chalk_1.default.cyan('@kk5Ai_world')}`);
|
|
462
|
+
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
463
|
+
});
|
|
464
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clawdpad",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for AI agents to launch memecoins on Clawdpad",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"clawdpad": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "ts-node src/index.ts",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"memecoin",
|
|
16
|
+
"token",
|
|
17
|
+
"ai",
|
|
18
|
+
"agent",
|
|
19
|
+
"base",
|
|
20
|
+
"clawdpad",
|
|
21
|
+
"x402"
|
|
22
|
+
],
|
|
23
|
+
"author": "Clawdpad",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"commander": "^11.1.0",
|
|
27
|
+
"chalk": "^4.1.2",
|
|
28
|
+
"ora": "^5.4.1",
|
|
29
|
+
"inquirer": "^8.2.6",
|
|
30
|
+
"node-fetch": "^2.7.0",
|
|
31
|
+
"viem": "^2.0.0",
|
|
32
|
+
"dotenv": "^16.3.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/inquirer": "^9.0.7",
|
|
36
|
+
"@types/node": "^20.10.0",
|
|
37
|
+
"@types/node-fetch": "^2.6.9",
|
|
38
|
+
"typescript": "^5.3.0",
|
|
39
|
+
"ts-node": "^10.9.2"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/clawdpad/clawdpad-cli"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import fetch from 'node-fetch';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
import { createPublicClient, http, formatEther, createWalletClient } from 'viem';
|
|
11
|
+
import { base, baseSepolia } from 'viem/chains';
|
|
12
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
13
|
+
|
|
14
|
+
const API_URL = process.env.CLAWDPAD_API_URL || 'https://www.clawdpad.ai/api';
|
|
15
|
+
const CONFIG_DIR = path.join(os.homedir(), '.clawdpad');
|
|
16
|
+
const WALLET_FILE = path.join(CONFIG_DIR, 'wallet.json');
|
|
17
|
+
const LAUNCHES_FILE = path.join(CONFIG_DIR, 'launches.json');
|
|
18
|
+
|
|
19
|
+
// Clanker API (for token deployment)
|
|
20
|
+
const CLANKER_API = 'https://www.clanker.world/api/v1';
|
|
21
|
+
|
|
22
|
+
const program = new Command();
|
|
23
|
+
|
|
24
|
+
// ASCII Art Banner
|
|
25
|
+
const banner = `
|
|
26
|
+
${chalk.cyan(' ██████╗██╗ █████╗ ██╗ ██╗██████╗ ██████╗ █████╗ ██████╗ ')}
|
|
27
|
+
${chalk.cyan(' ██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔══██╗██╔══██╗')}
|
|
28
|
+
${chalk.cyan(' ██║ ██║ ███████║██║ █╗ ██║██║ ██║██████╔╝███████║██║ ██║')}
|
|
29
|
+
${chalk.cyan(' ██║ ██║ ██╔══██║██║███╗██║██║ ██║██╔═══╝ ██╔══██║██║ ██║')}
|
|
30
|
+
${chalk.cyan(' ╚██████╗███████╗██║ ██║╚███╔███╔╝██████╔╝██║ ██║ ██║██████╔╝')}
|
|
31
|
+
${chalk.cyan(' ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ')}
|
|
32
|
+
${chalk.gray(' one command. your token. live on base.')}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
// ============ Wallet Management ============
|
|
36
|
+
|
|
37
|
+
interface WalletData {
|
|
38
|
+
address: string;
|
|
39
|
+
privateKey: string;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface LaunchRecord {
|
|
44
|
+
tokenAddress: string;
|
|
45
|
+
name: string;
|
|
46
|
+
symbol: string;
|
|
47
|
+
network: string;
|
|
48
|
+
transactionHash: string;
|
|
49
|
+
launchedAt: string;
|
|
50
|
+
website?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ensureConfigDir(): void {
|
|
54
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
55
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function loadWallet(): WalletData | null {
|
|
60
|
+
if (!fs.existsSync(WALLET_FILE)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const data = fs.readFileSync(WALLET_FILE, 'utf-8');
|
|
65
|
+
return JSON.parse(data);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createWallet(): WalletData {
|
|
72
|
+
ensureConfigDir();
|
|
73
|
+
|
|
74
|
+
const privateKey = generatePrivateKey();
|
|
75
|
+
const account = privateKeyToAccount(privateKey);
|
|
76
|
+
|
|
77
|
+
const wallet: WalletData = {
|
|
78
|
+
address: account.address,
|
|
79
|
+
privateKey: privateKey,
|
|
80
|
+
createdAt: new Date().toISOString(),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
fs.writeFileSync(WALLET_FILE, JSON.stringify(wallet, null, 2), { mode: 0o600 });
|
|
84
|
+
|
|
85
|
+
return wallet;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getOrCreateWallet(): { wallet: WalletData; isNew: boolean } {
|
|
89
|
+
const existing = loadWallet();
|
|
90
|
+
if (existing) {
|
|
91
|
+
return { wallet: existing, isNew: false };
|
|
92
|
+
}
|
|
93
|
+
return { wallet: createWallet(), isNew: true };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function loadLaunches(): LaunchRecord[] {
|
|
97
|
+
if (!fs.existsSync(LAUNCHES_FILE)) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const data = fs.readFileSync(LAUNCHES_FILE, 'utf-8');
|
|
102
|
+
return JSON.parse(data);
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function saveLaunch(launch: LaunchRecord): void {
|
|
109
|
+
ensureConfigDir();
|
|
110
|
+
const launches = loadLaunches();
|
|
111
|
+
launches.push(launch);
|
|
112
|
+
fs.writeFileSync(LAUNCHES_FILE, JSON.stringify(launches, null, 2));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ============ Image Upload ============
|
|
116
|
+
|
|
117
|
+
async function uploadImage(imagePath: string): Promise<string> {
|
|
118
|
+
// Read image file
|
|
119
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
120
|
+
const base64 = imageBuffer.toString('base64');
|
|
121
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
122
|
+
const mimeType = ext === '.png' ? 'image/png' :
|
|
123
|
+
ext === '.gif' ? 'image/gif' :
|
|
124
|
+
ext === '.webp' ? 'image/webp' : 'image/jpeg';
|
|
125
|
+
|
|
126
|
+
// Upload to Clawdpad (which uses imgbb or similar)
|
|
127
|
+
const response = await fetch(`${API_URL}/upload`, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: { 'Content-Type': 'application/json' },
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
image: `data:${mimeType};base64,${base64}`,
|
|
132
|
+
name: path.basename(imagePath, ext),
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const result = await response.json() as any;
|
|
137
|
+
|
|
138
|
+
if (!result.success || !result.url) {
|
|
139
|
+
throw new Error(result.error || 'Image upload failed');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return result.url;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============ Token Launch via Clanker ============
|
|
146
|
+
|
|
147
|
+
async function launchViaClanker(params: {
|
|
148
|
+
name: string;
|
|
149
|
+
symbol: string;
|
|
150
|
+
description: string;
|
|
151
|
+
image: string;
|
|
152
|
+
website?: string;
|
|
153
|
+
wallet: WalletData;
|
|
154
|
+
testnet?: boolean;
|
|
155
|
+
}): Promise<{
|
|
156
|
+
tokenAddress: string;
|
|
157
|
+
transactionHash: string;
|
|
158
|
+
clankerUrl: string;
|
|
159
|
+
explorerUrl: string;
|
|
160
|
+
}> {
|
|
161
|
+
// For now, use Clawdpad API which proxies to Clanker
|
|
162
|
+
// This allows us to sponsor gas and handle the complexity
|
|
163
|
+
|
|
164
|
+
const response = await fetch(`${API_URL}/launch`, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: { 'Content-Type': 'application/json' },
|
|
167
|
+
body: JSON.stringify({
|
|
168
|
+
name: params.name,
|
|
169
|
+
symbol: params.symbol,
|
|
170
|
+
description: params.description,
|
|
171
|
+
image: params.image,
|
|
172
|
+
website: params.website,
|
|
173
|
+
creatorWallet: params.wallet.address,
|
|
174
|
+
testnet: params.testnet,
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const result = await response.json() as any;
|
|
179
|
+
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
throw new Error(result.error || 'Launch failed');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const chainName = params.testnet ? 'base-sepolia' : 'base';
|
|
185
|
+
const explorerBase = params.testnet ? 'https://sepolia.basescan.org' : 'https://basescan.org';
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
tokenAddress: result.token?.address || result.tokenAddress || '0x...',
|
|
189
|
+
transactionHash: result.transactionHash || result.tx_hash || '0x...',
|
|
190
|
+
clankerUrl: `https://clanker.world/clanker/${result.token?.address || result.tokenAddress}`,
|
|
191
|
+
explorerUrl: `${explorerBase}/token/${result.token?.address || result.tokenAddress}`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============ CLI Commands ============
|
|
196
|
+
|
|
197
|
+
program
|
|
198
|
+
.name('clawdpad')
|
|
199
|
+
.description('Launch memecoins on Base — one command, no gas, earn fees')
|
|
200
|
+
.version('1.0.0');
|
|
201
|
+
|
|
202
|
+
// Main launch command
|
|
203
|
+
program
|
|
204
|
+
.option('--name <name>', 'Token name')
|
|
205
|
+
.option('--symbol <symbol>', 'Token symbol')
|
|
206
|
+
.option('--description <text>', 'Token description')
|
|
207
|
+
.option('--image <path>', 'Path to image (PNG/JPG/GIF/WebP, max 5MB)')
|
|
208
|
+
.option('--website <url>', 'Website URL (e.g., Moltbook post)')
|
|
209
|
+
.option('--testnet', 'Use Base Sepolia testnet')
|
|
210
|
+
.option('--json', 'Output JSON (machine-readable)')
|
|
211
|
+
.action(async (options) => {
|
|
212
|
+
const jsonMode = options.json;
|
|
213
|
+
|
|
214
|
+
if (!jsonMode) {
|
|
215
|
+
console.log(banner);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check if this is a subcommand or launch
|
|
219
|
+
if (!options.name && !options.symbol) {
|
|
220
|
+
if (!jsonMode) {
|
|
221
|
+
program.help();
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Validate required fields
|
|
227
|
+
if (!options.name || !options.symbol) {
|
|
228
|
+
const error = { success: false, error: 'Missing required fields: --name and --symbol' };
|
|
229
|
+
if (jsonMode) {
|
|
230
|
+
console.log(JSON.stringify(error));
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
console.error(chalk.red('Error: --name and --symbol are required'));
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const spinner = jsonMode ? null : ora('Preparing launch...').start();
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
// Get or create wallet
|
|
241
|
+
const { wallet, isNew } = getOrCreateWallet();
|
|
242
|
+
|
|
243
|
+
if (spinner) {
|
|
244
|
+
spinner.text = isNew ? 'Created new wallet...' : 'Using existing wallet...';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Upload image if provided
|
|
248
|
+
let imageUrl = '';
|
|
249
|
+
if (options.image) {
|
|
250
|
+
if (spinner) spinner.text = 'Uploading image...';
|
|
251
|
+
|
|
252
|
+
if (options.image.startsWith('http')) {
|
|
253
|
+
imageUrl = options.image;
|
|
254
|
+
} else if (fs.existsSync(options.image)) {
|
|
255
|
+
imageUrl = await uploadImage(options.image);
|
|
256
|
+
} else {
|
|
257
|
+
throw new Error(`Image not found: ${options.image}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Launch token
|
|
262
|
+
if (spinner) spinner.text = 'Launching token on Base...';
|
|
263
|
+
|
|
264
|
+
const result = await launchViaClanker({
|
|
265
|
+
name: options.name,
|
|
266
|
+
symbol: options.symbol,
|
|
267
|
+
description: options.description || `${options.name} - Launched via Clawdpad`,
|
|
268
|
+
image: imageUrl,
|
|
269
|
+
website: options.website,
|
|
270
|
+
wallet,
|
|
271
|
+
testnet: options.testnet,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Save launch record
|
|
275
|
+
saveLaunch({
|
|
276
|
+
tokenAddress: result.tokenAddress,
|
|
277
|
+
name: options.name,
|
|
278
|
+
symbol: options.symbol,
|
|
279
|
+
network: options.testnet ? 'base-sepolia' : 'base',
|
|
280
|
+
transactionHash: result.transactionHash,
|
|
281
|
+
launchedAt: new Date().toISOString(),
|
|
282
|
+
website: options.website,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (spinner) spinner.succeed('Token launched!');
|
|
286
|
+
|
|
287
|
+
// Output
|
|
288
|
+
const output = {
|
|
289
|
+
success: true,
|
|
290
|
+
tokenAddress: result.tokenAddress,
|
|
291
|
+
transactionHash: result.transactionHash,
|
|
292
|
+
name: options.name,
|
|
293
|
+
symbol: options.symbol,
|
|
294
|
+
network: options.testnet ? 'Base Sepolia' : 'Base',
|
|
295
|
+
explorer: result.explorerUrl,
|
|
296
|
+
clanker: result.clankerUrl,
|
|
297
|
+
wallet: wallet.address,
|
|
298
|
+
...(isNew ? { privateKey: wallet.privateKey } : {}),
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
if (jsonMode) {
|
|
302
|
+
console.log(JSON.stringify(output, null, 2));
|
|
303
|
+
} else {
|
|
304
|
+
console.log('\n' + chalk.green('✓ Token launched successfully!'));
|
|
305
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
306
|
+
console.log(` ${chalk.gray('Name:')} ${chalk.white(options.name)}`);
|
|
307
|
+
console.log(` ${chalk.gray('Symbol:')} ${chalk.cyan(options.symbol)}`);
|
|
308
|
+
console.log(` ${chalk.gray('Network:')} ${chalk.blue(options.testnet ? 'Base Sepolia' : 'Base')}`);
|
|
309
|
+
console.log(` ${chalk.gray('Address:')} ${chalk.yellow(result.tokenAddress)}`);
|
|
310
|
+
console.log(` ${chalk.gray('Wallet:')} ${chalk.gray(wallet.address)}`);
|
|
311
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
312
|
+
console.log(`\n ${chalk.white('View on Basescan:')}`);
|
|
313
|
+
console.log(` ${chalk.cyan(result.explorerUrl)}`);
|
|
314
|
+
console.log(`\n ${chalk.white('View on Clanker:')}`);
|
|
315
|
+
console.log(` ${chalk.cyan(result.clankerUrl)}`);
|
|
316
|
+
|
|
317
|
+
if (isNew) {
|
|
318
|
+
console.log('\n' + chalk.yellow('⚠️ NEW WALLET CREATED'));
|
|
319
|
+
console.log(chalk.gray(' Save this private key — it won\'t be shown again:'));
|
|
320
|
+
console.log(` ${chalk.red(wallet.privateKey)}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log('\n' + chalk.gray('Earn 80% of all trading fees! Check with: clawdpad fees'));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
} catch (error: any) {
|
|
327
|
+
if (spinner) spinner.fail('Launch failed');
|
|
328
|
+
|
|
329
|
+
const errorOutput = { success: false, error: error.message };
|
|
330
|
+
|
|
331
|
+
if (jsonMode) {
|
|
332
|
+
console.log(JSON.stringify(errorOutput));
|
|
333
|
+
} else {
|
|
334
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
335
|
+
}
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Wallet command
|
|
341
|
+
program
|
|
342
|
+
.command('wallet')
|
|
343
|
+
.description('Show wallet address and balance')
|
|
344
|
+
.option('--json', 'Output JSON')
|
|
345
|
+
.action(async (options) => {
|
|
346
|
+
const { wallet, isNew } = getOrCreateWallet();
|
|
347
|
+
|
|
348
|
+
// Get balance
|
|
349
|
+
const client = createPublicClient({
|
|
350
|
+
chain: base,
|
|
351
|
+
transport: http(),
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const balance = await client.getBalance({ address: wallet.address as `0x${string}` });
|
|
355
|
+
|
|
356
|
+
const output = {
|
|
357
|
+
address: wallet.address,
|
|
358
|
+
balance: formatEther(balance),
|
|
359
|
+
balanceWei: balance.toString(),
|
|
360
|
+
isNew,
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
if (options.json) {
|
|
364
|
+
console.log(JSON.stringify(output, null, 2));
|
|
365
|
+
} else {
|
|
366
|
+
console.log(banner);
|
|
367
|
+
console.log(chalk.white('Wallet:'));
|
|
368
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
369
|
+
console.log(` Address: ${chalk.cyan(wallet.address)}`);
|
|
370
|
+
console.log(` Balance: ${chalk.green(formatEther(balance))} ETH`);
|
|
371
|
+
if (isNew) {
|
|
372
|
+
console.log(chalk.yellow('\n ⚠️ New wallet created'));
|
|
373
|
+
console.log(chalk.gray(` Private key saved to: ${WALLET_FILE}`));
|
|
374
|
+
}
|
|
375
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Status command
|
|
380
|
+
program
|
|
381
|
+
.command('status')
|
|
382
|
+
.description('List all launched tokens')
|
|
383
|
+
.option('--json', 'Output JSON')
|
|
384
|
+
.action(async (options) => {
|
|
385
|
+
const launches = loadLaunches();
|
|
386
|
+
|
|
387
|
+
if (options.json) {
|
|
388
|
+
console.log(JSON.stringify({ launches }, null, 2));
|
|
389
|
+
} else {
|
|
390
|
+
console.log(banner);
|
|
391
|
+
console.log(chalk.white(`Launched Tokens (${launches.length}):`));
|
|
392
|
+
console.log(chalk.gray('─'.repeat(70)));
|
|
393
|
+
|
|
394
|
+
if (launches.length === 0) {
|
|
395
|
+
console.log(chalk.gray(' No tokens launched yet. Run: clawdpad --name "MyToken" --symbol "TKN"'));
|
|
396
|
+
} else {
|
|
397
|
+
for (const launch of launches) {
|
|
398
|
+
console.log(
|
|
399
|
+
` ${chalk.cyan(launch.symbol.padEnd(10))} ` +
|
|
400
|
+
`${chalk.white(launch.name.slice(0, 20).padEnd(22))} ` +
|
|
401
|
+
`${chalk.gray(launch.tokenAddress.slice(0, 16) + '...')}`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
console.log(chalk.gray('─'.repeat(70)));
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Fees command
|
|
410
|
+
program
|
|
411
|
+
.command('fees')
|
|
412
|
+
.description('Check claimable fees')
|
|
413
|
+
.option('--json', 'Output JSON')
|
|
414
|
+
.action(async (options) => {
|
|
415
|
+
const wallet = loadWallet();
|
|
416
|
+
|
|
417
|
+
if (!wallet) {
|
|
418
|
+
const error = { success: false, error: 'No wallet found. Launch a token first.' };
|
|
419
|
+
if (options.json) {
|
|
420
|
+
console.log(JSON.stringify(error));
|
|
421
|
+
} else {
|
|
422
|
+
console.error(chalk.red('No wallet found. Launch a token first.'));
|
|
423
|
+
}
|
|
424
|
+
process.exit(2);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Check fees via Clanker FeeLocker
|
|
428
|
+
// For now, return placeholder
|
|
429
|
+
const output = {
|
|
430
|
+
wallet: wallet.address,
|
|
431
|
+
claimableWETH: '0',
|
|
432
|
+
claimableTokens: '0',
|
|
433
|
+
canClaim: false,
|
|
434
|
+
hasGas: false,
|
|
435
|
+
hint: 'Fees accumulate when people trade your tokens',
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
if (options.json) {
|
|
439
|
+
console.log(JSON.stringify(output, null, 2));
|
|
440
|
+
} else {
|
|
441
|
+
console.log(banner);
|
|
442
|
+
console.log(chalk.white('Claimable Fees:'));
|
|
443
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
444
|
+
console.log(` Wallet: ${chalk.gray(wallet.address)}`);
|
|
445
|
+
console.log(` WETH Fees: ${chalk.green(output.claimableWETH)} WETH`);
|
|
446
|
+
console.log(` Can Claim: ${output.canClaim ? chalk.green('Yes') : chalk.yellow('No')}`);
|
|
447
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
448
|
+
console.log(chalk.gray('\n Fees accumulate when people trade your tokens.'));
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Claim command
|
|
453
|
+
program
|
|
454
|
+
.command('claim')
|
|
455
|
+
.description('Withdraw accumulated fees')
|
|
456
|
+
.option('--json', 'Output JSON')
|
|
457
|
+
.action(async (options) => {
|
|
458
|
+
const wallet = loadWallet();
|
|
459
|
+
|
|
460
|
+
if (!wallet) {
|
|
461
|
+
const error = { success: false, error: 'No wallet found' };
|
|
462
|
+
if (options.json) {
|
|
463
|
+
console.log(JSON.stringify(error));
|
|
464
|
+
} else {
|
|
465
|
+
console.error(chalk.red('No wallet found. Launch a token first.'));
|
|
466
|
+
}
|
|
467
|
+
process.exit(2);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// TODO: Implement actual fee claiming via Clanker FeeLocker contract
|
|
471
|
+
const output = {
|
|
472
|
+
success: false,
|
|
473
|
+
error: 'No fees to claim yet. Keep promoting your tokens!',
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
if (options.json) {
|
|
477
|
+
console.log(JSON.stringify(output));
|
|
478
|
+
} else {
|
|
479
|
+
console.log(banner);
|
|
480
|
+
console.log(chalk.yellow('No fees to claim yet.'));
|
|
481
|
+
console.log(chalk.gray('Keep promoting your tokens! Fees accumulate from trades.'));
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Info command
|
|
486
|
+
program
|
|
487
|
+
.command('info')
|
|
488
|
+
.description('About Clawdpad')
|
|
489
|
+
.action(() => {
|
|
490
|
+
console.log(banner);
|
|
491
|
+
console.log(chalk.white('\nWhat is Clawdpad?'));
|
|
492
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
493
|
+
console.log(' Clawdpad lets AI agents launch tokens on Base');
|
|
494
|
+
console.log(' with one command. No wallet setup, no gas fees.');
|
|
495
|
+
console.log('');
|
|
496
|
+
console.log(chalk.white('How it works:'));
|
|
497
|
+
console.log(' 1. Run: clawdpad --name "X" --symbol "X"');
|
|
498
|
+
console.log(' 2. Token deploys on Base via Clanker');
|
|
499
|
+
console.log(' 3. Earn 80% of all trading fees');
|
|
500
|
+
console.log(' 4. Claim fees anytime: clawdpad claim');
|
|
501
|
+
console.log('');
|
|
502
|
+
console.log(chalk.white('Links:'));
|
|
503
|
+
console.log(` Website: ${chalk.cyan('https://www.clawdpad.ai')}`);
|
|
504
|
+
console.log(` Token: ${chalk.cyan('$CLAWDPAD on Base')}`);
|
|
505
|
+
console.log(` Twitter: ${chalk.cyan('@kk5Ai_world')}`);
|
|
506
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
program.parse();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2022", "DOM"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|