clawncher 0.1.8 → 0.1.11

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 CHANGED
@@ -34,6 +34,11 @@ Deploy a new token on Base with Uniswap V4 pool:
34
34
  # Basic deployment
35
35
  clawncher deploy --name "My Token" --symbol MYTKN
36
36
 
37
+ # Pay the mandatory 1,000,000 $CLAWNCH launch burn (required to deploy while
38
+ # burn enforcement is on). Your wallet must hold >= 1,000,000 $CLAWNCH; the CLI
39
+ # burns it to the dead address, then deploys — in one command.
40
+ clawncher deploy --name "My Token" --symbol MYTKN --burn
41
+
37
42
  # With image and description
38
43
  clawncher deploy --name "My Token" --symbol MYTKN \
39
44
  --image "https://example.com/logo.png" \
package/dist/cli.js CHANGED
@@ -9,13 +9,29 @@ import chalk from 'chalk';
9
9
  import ora from 'ora';
10
10
  import Table from 'cli-table3';
11
11
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
12
- import { join } from 'path';
12
+ import { join, dirname } from 'path';
13
13
  import { homedir } from 'os';
14
+ import { fileURLToPath } from 'url';
14
15
  import { createWalletClient, createPublicClient, http, formatEther, parseEther, } from 'viem';
15
16
  import { privateKeyToAccount } from 'viem/accounts';
16
17
  import { base, baseSepolia } from 'viem/chains';
17
- import { ClawnchReader, ClawnchClient, ClawncherClaimer, ClawnchPortfolio, ClawnchWatcher, ClawnchSwapper, ClawnchLiquidity, ClawnchApiDeployer, WayfinderClient, getAddresses, NATIVE_TOKEN_ADDRESS, WAYFINDER_CHAIN_NAMES, ClawnchFeeLockerABI, HerdIntelligence, HerdAuth, } from '@clawnch/clawncher-sdk';
18
- const VERSION = '0.1.1';
18
+ import { ClawnchReader, ClawnchClient, ClawncherClaimer, ClawnchPortfolio, ClawnchWatcher, ClawnchSwapper, ClawnchLiquidity, ClawnchApiDeployer, WayfinderClient, BankrFeeClaimer, getAddresses, NATIVE_TOKEN_ADDRESS, WAYFINDER_CHAIN_NAMES, ClawnchFeeLockerABI, HerdIntelligence, HerdAuth, } from '@clawnch/clawncher-sdk';
19
+ // Read VERSION from package.json at runtime instead of hardcoding it.
20
+ // The previous hardcoded constant drifted from the published package
21
+ // version on every release and showed users an inaccurate banner.
22
+ // dist/cli.js → ../package.json (package root).
23
+ const VERSION = (() => {
24
+ try {
25
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
26
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
27
+ return pkg.version || '0.0.0';
28
+ }
29
+ catch {
30
+ // package.json missing / unreadable — return a sentinel that's
31
+ // clearly wrong so the bug is visible instead of silently stale.
32
+ return '0.0.0';
33
+ }
34
+ })();
19
35
  // ============================================================================
20
36
  // UI Toolkit
21
37
  // ============================================================================
@@ -329,7 +345,8 @@ program
329
345
  .option('--dev-buy-recipient <address>', 'Dev buy recipient')
330
346
  .option('--pair <token>', 'Paired token: "weth" (default) or "usdc"', 'weth')
331
347
  .option('--no-vanity', 'Disable vanity address')
332
- .option('--bypass-rate-limit', 'Burn 10,000 $CLAWNCH to skip 1-per-hour cooldown')
348
+ .option('--bypass-rate-limit', 'Burn 10,000 $CLAWNCH to skip 24h per-agent cooldown')
349
+ .option('--burn', 'Pay the mandatory 1,000,000 $CLAWNCH launch burn (required when burn enforcement is on)')
333
350
  .option('--network <network>', 'Network (mainnet/sepolia)', 'mainnet')
334
351
  .option('--private-key <key>', 'Private key')
335
352
  .option('--rpc <url>', 'RPC URL')
@@ -354,6 +371,7 @@ program
354
371
  name: opts.name,
355
372
  symbol: opts.symbol,
356
373
  bypassRateLimit: opts.bypassRateLimit || false,
374
+ burn: opts.burn || false,
357
375
  };
358
376
  if (opts.image)
359
377
  request.image = opts.image;
@@ -391,7 +409,9 @@ program
391
409
  recipient: opts.devBuyRecipient ? validateAddress(opts.devBuyRecipient, 'dev buy recipient') : account.address,
392
410
  };
393
411
  }
394
- spinner.text = `Deploying ${opts.symbol} on ${network}...`;
412
+ spinner.text = opts.burn
413
+ ? `Burning 1,000,000 $CLAWNCH + deploying ${opts.symbol} on ${network}...`
414
+ : `Deploying ${opts.symbol} on ${network}...`;
395
415
  const result = await deployer.deploy(request);
396
416
  spinner.succeed('Token deployed');
397
417
  if (opts.json) {
@@ -421,6 +441,9 @@ program
421
441
  }
422
442
  catch (err) {
423
443
  spinner.stop();
444
+ if (!opts.burn && err instanceof Error && /burn/i.test(err.message)) {
445
+ console.error(`\n ${c.muted('This launch path requires a 1,000,000 $CLAWNCH burn. Re-run with')} ${c.highlight('--burn')} ${c.muted('to burn + deploy in one step.')}\n`);
446
+ }
424
447
  handleError(err);
425
448
  }
426
449
  });
@@ -873,6 +896,191 @@ fees
873
896
  }
874
897
  });
875
898
  // ============================================================================
899
+ // Bankr Fee Commands (Doppler protocol)
900
+ // ============================================================================
901
+ fees
902
+ .command('bankr-check')
903
+ .description('Check claimable Bankr/Doppler fees for a wallet')
904
+ .argument('<wallet>', 'Wallet address')
905
+ .option('-d, --days <days>', 'Lookback period in days (1-90)', '30')
906
+ .option('--json', 'Output as JSON')
907
+ .action(async (wallet, opts) => {
908
+ const walletAddr = validateAddress(wallet, 'wallet');
909
+ const spinner = ora('Checking Bankr fees...').start();
910
+ try {
911
+ const days = parseInt(opts.days, 10);
912
+ const dashboard = await BankrFeeClaimer.getCreatorFees(walletAddr, days);
913
+ spinner.stop();
914
+ if (opts.json) {
915
+ console.log(JSON.stringify(dashboard, null, 2));
916
+ return;
917
+ }
918
+ console.log();
919
+ console.log(sectionHeader('Bankr Fee Dashboard'));
920
+ console.log(kv('Wallet', fmtAddr(wallet)));
921
+ console.log(kv('Period', c.value(`${dashboard.days} days`)));
922
+ console.log();
923
+ console.log(kv('Claimable WETH', c.accent(dashboard.totals.claimableWeth + ' WETH')));
924
+ console.log(kv('Claimed WETH', c.muted(dashboard.totals.claimedWeth + ' WETH')));
925
+ console.log(kv('Claim Count', c.value(dashboard.totals.claimCount.toString())));
926
+ console.log();
927
+ if (!dashboard.tokens || dashboard.tokens.length === 0) {
928
+ console.log(c.muted(' No Bankr tokens with fees found'));
929
+ console.log();
930
+ return;
931
+ }
932
+ console.log(styledTable(['Symbol', 'Token', 'Claimable WETH', 'Claimed WETH'], dashboard.tokens.map((t) => {
933
+ const wethIsToken0 = t.token0Label === 'WETH';
934
+ const claimableWeth = wethIsToken0 ? t.claimable.token0 : t.claimable.token1;
935
+ const claimedWeth = wethIsToken0 ? t.claimed.token0 : t.claimed.token1;
936
+ return [
937
+ c.highlight(t.symbol),
938
+ fmtAddr(t.tokenAddress, true),
939
+ c.accent(claimableWeth),
940
+ c.muted(claimedWeth),
941
+ ];
942
+ })));
943
+ console.log();
944
+ }
945
+ catch (err) {
946
+ spinner.stop();
947
+ handleError(err);
948
+ }
949
+ });
950
+ fees
951
+ .command('bankr-claim')
952
+ .description('Claim Bankr/Doppler fees for a specific token')
953
+ .argument('<token>', 'Token contract address')
954
+ .option('--private-key <key>', 'Private key (or use CLAWNCHER_PRIVATE_KEY env)')
955
+ .option('--rpc <url>', 'Custom RPC URL')
956
+ .option('--json', 'Output as JSON')
957
+ .action(async (token, opts) => {
958
+ const spinner = ora('Preparing Bankr fee claim...').start();
959
+ try {
960
+ const tokenAddress = validateAddress(token, 'token');
961
+ const privateKey = await getPrivateKeyOrWallet(opts);
962
+ const rpcUrl = opts.rpc || 'https://mainnet.base.org';
963
+ const account = privateKeyToAccount(privateKey);
964
+ const wallet = createWalletClient({
965
+ account,
966
+ chain: base,
967
+ transport: http(rpcUrl),
968
+ });
969
+ const publicClient = createPublicClient({
970
+ chain: base,
971
+ transport: http(rpcUrl),
972
+ });
973
+ // First, look up this token's fee data
974
+ spinner.text = 'Looking up token fees...';
975
+ const dashboard = await BankrFeeClaimer.getCreatorFees(account.address);
976
+ const tokenData = dashboard.tokens?.find((t) => t.tokenAddress.toLowerCase() === tokenAddress.toLowerCase());
977
+ if (!tokenData) {
978
+ spinner.stop();
979
+ console.log();
980
+ console.log(c.error(' No Bankr fees found for this token'));
981
+ console.log(c.muted(' Make sure this is a Bankr-deployed token and you are the creator'));
982
+ console.log();
983
+ return;
984
+ }
985
+ if (!tokenData.initializer || !tokenData.poolId) {
986
+ spinner.stop();
987
+ console.log();
988
+ console.log(c.error(' Missing initializer or poolId — cannot claim'));
989
+ console.log();
990
+ return;
991
+ }
992
+ const wethIsToken0 = tokenData.token0Label === 'WETH';
993
+ const claimableWeth = wethIsToken0 ? tokenData.claimable.token0 : tokenData.claimable.token1;
994
+ spinner.text = `Claiming ${tokenData.symbol} fees (${claimableWeth} WETH)...`;
995
+ const claimer = new BankrFeeClaimer({ wallet, publicClient });
996
+ const result = await claimer.claimToken(tokenData);
997
+ spinner.stop();
998
+ if (opts.json) {
999
+ console.log(JSON.stringify(result, null, 2));
1000
+ return;
1001
+ }
1002
+ console.log();
1003
+ console.log(sectionHeader('Bankr Fee Claim'));
1004
+ console.log(kv('Token', `${c.highlight(tokenData.symbol)} ${fmtAddr(tokenAddress, true)}`));
1005
+ console.log(kv('Caller', fmtAddr(account.address)));
1006
+ console.log();
1007
+ if (result.success) {
1008
+ console.log(` ${c.success('\u2713')} ${c.label('Claimed')} ${c.accent(claimableWeth + ' WETH')}`);
1009
+ console.log(` ${c.muted('tx:')} ${c.dim(result.txHash)}`);
1010
+ }
1011
+ else {
1012
+ console.log(` ${c.error('\u2717')} ${c.label('Failed')} ${c.error(result.error || 'Unknown error')}`);
1013
+ }
1014
+ console.log();
1015
+ }
1016
+ catch (err) {
1017
+ spinner.stop();
1018
+ handleError(err);
1019
+ }
1020
+ });
1021
+ fees
1022
+ .command('bankr-claim-all')
1023
+ .description('Claim Bankr/Doppler fees for all tokens with claimable fees')
1024
+ .option('--private-key <key>', 'Private key (or use CLAWNCHER_PRIVATE_KEY env)')
1025
+ .option('--rpc <url>', 'Custom RPC URL')
1026
+ .option('--json', 'Output as JSON')
1027
+ .action(async (opts) => {
1028
+ const spinner = ora('Scanning for claimable Bankr fees...').start();
1029
+ try {
1030
+ const privateKey = await getPrivateKeyOrWallet(opts);
1031
+ const rpcUrl = opts.rpc || 'https://mainnet.base.org';
1032
+ const account = privateKeyToAccount(privateKey);
1033
+ const wallet = createWalletClient({
1034
+ account,
1035
+ chain: base,
1036
+ transport: http(rpcUrl),
1037
+ });
1038
+ const publicClient = createPublicClient({
1039
+ chain: base,
1040
+ transport: http(rpcUrl),
1041
+ });
1042
+ const claimer = new BankrFeeClaimer({ wallet, publicClient });
1043
+ const result = await claimer.claimAll({
1044
+ onProgress: (progress) => {
1045
+ spinner.text = `Scanning launches... ${progress.scanned}/${progress.total}`;
1046
+ },
1047
+ onClaim: (claimResult, index, total) => {
1048
+ const icon = claimResult.success ? c.success('\u2713') : c.error('\u2717');
1049
+ spinner.text = `${icon} ${claimResult.symbol} (${index + 1}/${total})`;
1050
+ },
1051
+ });
1052
+ spinner.stop();
1053
+ if (opts.json) {
1054
+ console.log(JSON.stringify(result, null, 2));
1055
+ return;
1056
+ }
1057
+ console.log();
1058
+ console.log(sectionHeader('Bankr Batch Claim Results'));
1059
+ console.log(kv('Wallet', fmtAddr(account.address)));
1060
+ console.log(kv('WETH Estimate', c.accent(result.totalWethEstimate + ' WETH')));
1061
+ console.log();
1062
+ if (result.results.length === 0) {
1063
+ console.log(c.muted(' No claimable Bankr fees found'));
1064
+ console.log();
1065
+ return;
1066
+ }
1067
+ for (const r of result.results) {
1068
+ const icon = r.success ? c.success('\u2713') : c.error('\u2717');
1069
+ const status = r.success
1070
+ ? c.dim(r.txHash.slice(0, 14) + '...')
1071
+ : c.error(r.error || 'failed');
1072
+ console.log(` ${icon} ${c.highlight(r.symbol.padEnd(10))} ${fmtAddr(r.tokenAddress, true)} ${c.dim('\u2500')} ${status}`);
1073
+ }
1074
+ console.log();
1075
+ console.log(c.dim(` ${result.successCount}/${result.results.length} tokens claimed successfully`));
1076
+ console.log();
1077
+ }
1078
+ catch (err) {
1079
+ spinner.stop();
1080
+ handleError(err);
1081
+ }
1082
+ });
1083
+ // ============================================================================
876
1084
  // Portfolio Command
877
1085
  // ============================================================================
878
1086
  program
@@ -1120,6 +1328,158 @@ program
1120
1328
  }
1121
1329
  });
1122
1330
  // ============================================================================
1331
+ // Connect Command (Telegram Bot Pairing)
1332
+ // ============================================================================
1333
+ program
1334
+ .command('connect')
1335
+ .description('Connect to a wallet set up via the Telegram bot (@OpenClawnchBot)')
1336
+ .argument('<code>', 'Pairing code from the Telegram bot (e.g. CLAW-A7X9)')
1337
+ .option('--api-url <url>', 'Clawnch API URL')
1338
+ .option('--json', 'Output as JSON')
1339
+ .action(async (code, opts) => {
1340
+ const spinner = ora('Connecting...').start();
1341
+ try {
1342
+ const baseUrl = (opts.apiUrl || getClawnchApiUrl()).replace(/\/$/, '');
1343
+ // Strip ALL whitespace (not just leading/trailing) — paste from the
1344
+ // Telegram bot occasionally includes a non-breaking space or a stray
1345
+ // newline between the prefix and the random suffix. The previous
1346
+ // `.trim()` only handled outer whitespace, which made codes that
1347
+ // looked right to the user fail the server-side `^CLAW-[A-Z0-9]{4}$`
1348
+ // regex with no useful feedback.
1349
+ const normalizedCode = code.replace(/\s+/g, '').toUpperCase();
1350
+ // Exchange pairing code for encrypted credentials
1351
+ spinner.text = 'Exchanging pairing code...';
1352
+ const res = await fetch(`${baseUrl}/api/pair/exchange`, {
1353
+ method: 'POST',
1354
+ headers: { 'Content-Type': 'application/json' },
1355
+ body: JSON.stringify({ code: normalizedCode }),
1356
+ });
1357
+ if (!res.ok) {
1358
+ const err = await res.json().catch(() => ({ error: 'Exchange failed' }));
1359
+ spinner.fail(`Failed: ${err.error || 'Unknown error'}`);
1360
+ // Echo the exact string we sent. Codes are single-use + 5-minute TTL
1361
+ // so this isn't a secret leak by the time the user sees the error,
1362
+ // and it's the only way to debug "I pasted the right thing"
1363
+ // mismatches caused by invisible characters surviving the trim.
1364
+ // JSON.stringify reveals invisible chars as escape sequences.
1365
+ console.log(c.muted(` Sent code: ${JSON.stringify(normalizedCode)}`));
1366
+ console.log(c.muted(` Expected format: CLAW-XXXX (4 alphanumeric chars, uppercase)`));
1367
+ if (err.suggestion)
1368
+ console.log(c.muted(` ${err.suggestion}`));
1369
+ process.exit(1);
1370
+ }
1371
+ const data = await res.json();
1372
+ spinner.text = 'Decrypting wallet...';
1373
+ // Ask for PIN to decrypt the private key
1374
+ const { createInterface } = await import('readline');
1375
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1376
+ const pin = await new Promise((resolve) => {
1377
+ // Use stderr for the prompt so it doesn't interfere with JSON output
1378
+ process.stderr.write(c.label('\n Enter the PIN you set in the Telegram bot: '));
1379
+ rl.question('', (answer) => {
1380
+ rl.close();
1381
+ resolve(answer.trim());
1382
+ });
1383
+ });
1384
+ if (!pin) {
1385
+ spinner.fail('PIN is required to decrypt the wallet.');
1386
+ process.exit(1);
1387
+ }
1388
+ // Decrypt private key
1389
+ const { scryptSync, createDecipheriv } = await import('crypto');
1390
+ const salt = Buffer.from(data.salt, 'hex');
1391
+ const iv = Buffer.from(data.iv, 'hex');
1392
+ const authTag = Buffer.from(data.authTag, 'hex');
1393
+ const ciphertext = Buffer.from(data.encryptedKey, 'hex');
1394
+ let privateKey;
1395
+ try {
1396
+ // Match the bot's scrypt params (N=2^14, r=8, p=1)
1397
+ const key = scryptSync(pin, salt, 32, { N: 2 ** 14, r: 8, p: 1 });
1398
+ const decipher = createDecipheriv('aes-256-gcm', key, iv);
1399
+ decipher.setAuthTag(authTag);
1400
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
1401
+ privateKey = decrypted.toString('utf8');
1402
+ }
1403
+ catch {
1404
+ spinner.fail('Wrong PIN. Could not decrypt the wallet.');
1405
+ process.exit(1);
1406
+ }
1407
+ // Verify it's a valid private key
1408
+ if (!privateKey.startsWith('0x') || privateKey.length !== 66) {
1409
+ spinner.fail('Decrypted data is not a valid private key. Try again with /start in the bot.');
1410
+ process.exit(1);
1411
+ }
1412
+ // Import the wallet using the existing encrypted wallet system
1413
+ // Re-encrypt with the CLI's stronger scrypt params for local storage
1414
+ const { importFromPrivateKey, setActiveWallet, listWallets } = await import('./wallet.js');
1415
+ const walletName = data.agentName
1416
+ ? data.agentName.toLowerCase().replace(/[^a-z0-9-_]/g, '-')
1417
+ : `tg-${data.wallet.slice(2, 8).toLowerCase()}`;
1418
+ // Check if wallet name already exists
1419
+ const existing = listWallets();
1420
+ let finalName = walletName;
1421
+ if (existing.some((w) => w.name === walletName)) {
1422
+ finalName = `${walletName}-${Date.now().toString(36).slice(-4)}`;
1423
+ }
1424
+ // Prompt for a local password (stronger than PIN, for CLI encryption)
1425
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
1426
+ const password = await new Promise((resolve) => {
1427
+ process.stderr.write(c.label(' Choose a password for local wallet encryption (8+ chars): '));
1428
+ rl2.question('', (answer) => {
1429
+ rl2.close();
1430
+ resolve(answer.trim());
1431
+ });
1432
+ });
1433
+ if (password.length < 8) {
1434
+ spinner.fail('Password must be at least 8 characters.');
1435
+ process.exit(1);
1436
+ }
1437
+ spinner.text = 'Importing wallet...';
1438
+ // Import with the CLI's wallet system (uses scrypt N=2^18)
1439
+ importFromPrivateKey(finalName, privateKey, password);
1440
+ setActiveWallet(finalName);
1441
+ // Save agent config if present
1442
+ const config = loadConfig();
1443
+ config.network = data.network || 'mainnet';
1444
+ if (!config.privateKey) {
1445
+ // Don't overwrite existing raw key, but set network
1446
+ }
1447
+ saveConfig(config);
1448
+ spinner.succeed('Connected!');
1449
+ if (opts.json) {
1450
+ console.log(JSON.stringify({
1451
+ wallet: data.wallet,
1452
+ walletName: finalName,
1453
+ agentName: data.agentName,
1454
+ hasApiKey: !!data.agentApiKey,
1455
+ network: data.network,
1456
+ }, null, 2));
1457
+ }
1458
+ else {
1459
+ console.log();
1460
+ console.log(kv('Wallet', c.value(data.wallet)));
1461
+ console.log(kv('Wallet name', c.accent(finalName)));
1462
+ console.log(kv('Network', networkBadge(data.network || 'mainnet')));
1463
+ if (data.agentName) {
1464
+ console.log(kv('Agent', c.value(data.agentName)));
1465
+ }
1466
+ if (data.agentApiKey) {
1467
+ console.log(kv('API Key', c.accent(data.agentApiKey)));
1468
+ console.log();
1469
+ console.log(c.warn(' Save your API key — you\'ll need it for verified deploys.'));
1470
+ }
1471
+ console.log();
1472
+ console.log(c.muted(' Your wallet is encrypted and stored locally.'));
1473
+ console.log(c.muted(' Next: clawncher deploy --verified --api-key <key>'));
1474
+ console.log();
1475
+ }
1476
+ }
1477
+ catch (err) {
1478
+ spinner.stop();
1479
+ handleError(err);
1480
+ }
1481
+ });
1482
+ // ============================================================================
1123
1483
  // Tokens Command (API)
1124
1484
  // ============================================================================
1125
1485
  program