clawncher 0.1.8 → 0.1.9

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/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ import { homedir } from 'os';
14
14
  import { createWalletClient, createPublicClient, http, formatEther, parseEther, } from 'viem';
15
15
  import { privateKeyToAccount } from 'viem/accounts';
16
16
  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';
17
+ 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';
18
18
  const VERSION = '0.1.1';
19
19
  // ============================================================================
20
20
  // UI Toolkit
@@ -873,6 +873,191 @@ fees
873
873
  }
874
874
  });
875
875
  // ============================================================================
876
+ // Bankr Fee Commands (Doppler protocol)
877
+ // ============================================================================
878
+ fees
879
+ .command('bankr-check')
880
+ .description('Check claimable Bankr/Doppler fees for a wallet')
881
+ .argument('<wallet>', 'Wallet address')
882
+ .option('-d, --days <days>', 'Lookback period in days (1-90)', '30')
883
+ .option('--json', 'Output as JSON')
884
+ .action(async (wallet, opts) => {
885
+ const walletAddr = validateAddress(wallet, 'wallet');
886
+ const spinner = ora('Checking Bankr fees...').start();
887
+ try {
888
+ const days = parseInt(opts.days, 10);
889
+ const dashboard = await BankrFeeClaimer.getCreatorFees(walletAddr, days);
890
+ spinner.stop();
891
+ if (opts.json) {
892
+ console.log(JSON.stringify(dashboard, null, 2));
893
+ return;
894
+ }
895
+ console.log();
896
+ console.log(sectionHeader('Bankr Fee Dashboard'));
897
+ console.log(kv('Wallet', fmtAddr(wallet)));
898
+ console.log(kv('Period', c.value(`${dashboard.days} days`)));
899
+ console.log();
900
+ console.log(kv('Claimable WETH', c.accent(dashboard.totals.claimableWeth + ' WETH')));
901
+ console.log(kv('Claimed WETH', c.muted(dashboard.totals.claimedWeth + ' WETH')));
902
+ console.log(kv('Claim Count', c.value(dashboard.totals.claimCount.toString())));
903
+ console.log();
904
+ if (!dashboard.tokens || dashboard.tokens.length === 0) {
905
+ console.log(c.muted(' No Bankr tokens with fees found'));
906
+ console.log();
907
+ return;
908
+ }
909
+ console.log(styledTable(['Symbol', 'Token', 'Claimable WETH', 'Claimed WETH'], dashboard.tokens.map((t) => {
910
+ const wethIsToken0 = t.token0Label === 'WETH';
911
+ const claimableWeth = wethIsToken0 ? t.claimable.token0 : t.claimable.token1;
912
+ const claimedWeth = wethIsToken0 ? t.claimed.token0 : t.claimed.token1;
913
+ return [
914
+ c.highlight(t.symbol),
915
+ fmtAddr(t.tokenAddress, true),
916
+ c.accent(claimableWeth),
917
+ c.muted(claimedWeth),
918
+ ];
919
+ })));
920
+ console.log();
921
+ }
922
+ catch (err) {
923
+ spinner.stop();
924
+ handleError(err);
925
+ }
926
+ });
927
+ fees
928
+ .command('bankr-claim')
929
+ .description('Claim Bankr/Doppler fees for a specific token')
930
+ .argument('<token>', 'Token contract address')
931
+ .option('--private-key <key>', 'Private key (or use CLAWNCHER_PRIVATE_KEY env)')
932
+ .option('--rpc <url>', 'Custom RPC URL')
933
+ .option('--json', 'Output as JSON')
934
+ .action(async (token, opts) => {
935
+ const spinner = ora('Preparing Bankr fee claim...').start();
936
+ try {
937
+ const tokenAddress = validateAddress(token, 'token');
938
+ const privateKey = await getPrivateKeyOrWallet(opts);
939
+ const rpcUrl = opts.rpc || 'https://mainnet.base.org';
940
+ const account = privateKeyToAccount(privateKey);
941
+ const wallet = createWalletClient({
942
+ account,
943
+ chain: base,
944
+ transport: http(rpcUrl),
945
+ });
946
+ const publicClient = createPublicClient({
947
+ chain: base,
948
+ transport: http(rpcUrl),
949
+ });
950
+ // First, look up this token's fee data
951
+ spinner.text = 'Looking up token fees...';
952
+ const dashboard = await BankrFeeClaimer.getCreatorFees(account.address);
953
+ const tokenData = dashboard.tokens?.find((t) => t.tokenAddress.toLowerCase() === tokenAddress.toLowerCase());
954
+ if (!tokenData) {
955
+ spinner.stop();
956
+ console.log();
957
+ console.log(c.error(' No Bankr fees found for this token'));
958
+ console.log(c.muted(' Make sure this is a Bankr-deployed token and you are the creator'));
959
+ console.log();
960
+ return;
961
+ }
962
+ if (!tokenData.initializer || !tokenData.poolId) {
963
+ spinner.stop();
964
+ console.log();
965
+ console.log(c.error(' Missing initializer or poolId — cannot claim'));
966
+ console.log();
967
+ return;
968
+ }
969
+ const wethIsToken0 = tokenData.token0Label === 'WETH';
970
+ const claimableWeth = wethIsToken0 ? tokenData.claimable.token0 : tokenData.claimable.token1;
971
+ spinner.text = `Claiming ${tokenData.symbol} fees (${claimableWeth} WETH)...`;
972
+ const claimer = new BankrFeeClaimer({ wallet, publicClient });
973
+ const result = await claimer.claimToken(tokenData);
974
+ spinner.stop();
975
+ if (opts.json) {
976
+ console.log(JSON.stringify(result, null, 2));
977
+ return;
978
+ }
979
+ console.log();
980
+ console.log(sectionHeader('Bankr Fee Claim'));
981
+ console.log(kv('Token', `${c.highlight(tokenData.symbol)} ${fmtAddr(tokenAddress, true)}`));
982
+ console.log(kv('Caller', fmtAddr(account.address)));
983
+ console.log();
984
+ if (result.success) {
985
+ console.log(` ${c.success('\u2713')} ${c.label('Claimed')} ${c.accent(claimableWeth + ' WETH')}`);
986
+ console.log(` ${c.muted('tx:')} ${c.dim(result.txHash)}`);
987
+ }
988
+ else {
989
+ console.log(` ${c.error('\u2717')} ${c.label('Failed')} ${c.error(result.error || 'Unknown error')}`);
990
+ }
991
+ console.log();
992
+ }
993
+ catch (err) {
994
+ spinner.stop();
995
+ handleError(err);
996
+ }
997
+ });
998
+ fees
999
+ .command('bankr-claim-all')
1000
+ .description('Claim Bankr/Doppler fees for all tokens with claimable fees')
1001
+ .option('--private-key <key>', 'Private key (or use CLAWNCHER_PRIVATE_KEY env)')
1002
+ .option('--rpc <url>', 'Custom RPC URL')
1003
+ .option('--json', 'Output as JSON')
1004
+ .action(async (opts) => {
1005
+ const spinner = ora('Scanning for claimable Bankr fees...').start();
1006
+ try {
1007
+ const privateKey = await getPrivateKeyOrWallet(opts);
1008
+ const rpcUrl = opts.rpc || 'https://mainnet.base.org';
1009
+ const account = privateKeyToAccount(privateKey);
1010
+ const wallet = createWalletClient({
1011
+ account,
1012
+ chain: base,
1013
+ transport: http(rpcUrl),
1014
+ });
1015
+ const publicClient = createPublicClient({
1016
+ chain: base,
1017
+ transport: http(rpcUrl),
1018
+ });
1019
+ const claimer = new BankrFeeClaimer({ wallet, publicClient });
1020
+ const result = await claimer.claimAll({
1021
+ onProgress: (progress) => {
1022
+ spinner.text = `Scanning launches... ${progress.scanned}/${progress.total}`;
1023
+ },
1024
+ onClaim: (claimResult, index, total) => {
1025
+ const icon = claimResult.success ? c.success('\u2713') : c.error('\u2717');
1026
+ spinner.text = `${icon} ${claimResult.symbol} (${index + 1}/${total})`;
1027
+ },
1028
+ });
1029
+ spinner.stop();
1030
+ if (opts.json) {
1031
+ console.log(JSON.stringify(result, null, 2));
1032
+ return;
1033
+ }
1034
+ console.log();
1035
+ console.log(sectionHeader('Bankr Batch Claim Results'));
1036
+ console.log(kv('Wallet', fmtAddr(account.address)));
1037
+ console.log(kv('WETH Estimate', c.accent(result.totalWethEstimate + ' WETH')));
1038
+ console.log();
1039
+ if (result.results.length === 0) {
1040
+ console.log(c.muted(' No claimable Bankr fees found'));
1041
+ console.log();
1042
+ return;
1043
+ }
1044
+ for (const r of result.results) {
1045
+ const icon = r.success ? c.success('\u2713') : c.error('\u2717');
1046
+ const status = r.success
1047
+ ? c.dim(r.txHash.slice(0, 14) + '...')
1048
+ : c.error(r.error || 'failed');
1049
+ console.log(` ${icon} ${c.highlight(r.symbol.padEnd(10))} ${fmtAddr(r.tokenAddress, true)} ${c.dim('\u2500')} ${status}`);
1050
+ }
1051
+ console.log();
1052
+ console.log(c.dim(` ${result.successCount}/${result.results.length} tokens claimed successfully`));
1053
+ console.log();
1054
+ }
1055
+ catch (err) {
1056
+ spinner.stop();
1057
+ handleError(err);
1058
+ }
1059
+ });
1060
+ // ============================================================================
876
1061
  // Portfolio Command
877
1062
  // ============================================================================
878
1063
  program
@@ -1120,6 +1305,145 @@ program
1120
1305
  }
1121
1306
  });
1122
1307
  // ============================================================================
1308
+ // Connect Command (Telegram Bot Pairing)
1309
+ // ============================================================================
1310
+ program
1311
+ .command('connect')
1312
+ .description('Connect to a wallet set up via the Telegram bot (@OpenClawnchBot)')
1313
+ .argument('<code>', 'Pairing code from the Telegram bot (e.g. CLAW-A7X9)')
1314
+ .option('--api-url <url>', 'Clawnch API URL')
1315
+ .option('--json', 'Output as JSON')
1316
+ .action(async (code, opts) => {
1317
+ const spinner = ora('Connecting...').start();
1318
+ try {
1319
+ const baseUrl = (opts.apiUrl || getClawnchApiUrl()).replace(/\/$/, '');
1320
+ const normalizedCode = code.trim().toUpperCase();
1321
+ // Exchange pairing code for encrypted credentials
1322
+ spinner.text = 'Exchanging pairing code...';
1323
+ const res = await fetch(`${baseUrl}/api/pair/exchange`, {
1324
+ method: 'POST',
1325
+ headers: { 'Content-Type': 'application/json' },
1326
+ body: JSON.stringify({ code: normalizedCode }),
1327
+ });
1328
+ if (!res.ok) {
1329
+ const err = await res.json().catch(() => ({ error: 'Exchange failed' }));
1330
+ spinner.fail(`Failed: ${err.error || 'Unknown error'}`);
1331
+ if (err.suggestion)
1332
+ console.log(c.muted(` ${err.suggestion}`));
1333
+ process.exit(1);
1334
+ }
1335
+ const data = await res.json();
1336
+ spinner.text = 'Decrypting wallet...';
1337
+ // Ask for PIN to decrypt the private key
1338
+ const { createInterface } = await import('readline');
1339
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1340
+ const pin = await new Promise((resolve) => {
1341
+ // Use stderr for the prompt so it doesn't interfere with JSON output
1342
+ process.stderr.write(c.label('\n Enter the PIN you set in the Telegram bot: '));
1343
+ rl.question('', (answer) => {
1344
+ rl.close();
1345
+ resolve(answer.trim());
1346
+ });
1347
+ });
1348
+ if (!pin) {
1349
+ spinner.fail('PIN is required to decrypt the wallet.');
1350
+ process.exit(1);
1351
+ }
1352
+ // Decrypt private key
1353
+ const { scryptSync, createDecipheriv } = await import('crypto');
1354
+ const salt = Buffer.from(data.salt, 'hex');
1355
+ const iv = Buffer.from(data.iv, 'hex');
1356
+ const authTag = Buffer.from(data.authTag, 'hex');
1357
+ const ciphertext = Buffer.from(data.encryptedKey, 'hex');
1358
+ let privateKey;
1359
+ try {
1360
+ // Match the bot's scrypt params (N=2^14, r=8, p=1)
1361
+ const key = scryptSync(pin, salt, 32, { N: 2 ** 14, r: 8, p: 1 });
1362
+ const decipher = createDecipheriv('aes-256-gcm', key, iv);
1363
+ decipher.setAuthTag(authTag);
1364
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
1365
+ privateKey = decrypted.toString('utf8');
1366
+ }
1367
+ catch {
1368
+ spinner.fail('Wrong PIN. Could not decrypt the wallet.');
1369
+ process.exit(1);
1370
+ }
1371
+ // Verify it's a valid private key
1372
+ if (!privateKey.startsWith('0x') || privateKey.length !== 66) {
1373
+ spinner.fail('Decrypted data is not a valid private key. Try again with /start in the bot.');
1374
+ process.exit(1);
1375
+ }
1376
+ // Import the wallet using the existing encrypted wallet system
1377
+ // Re-encrypt with the CLI's stronger scrypt params for local storage
1378
+ const { importFromPrivateKey, setActiveWallet, listWallets } = await import('./wallet.js');
1379
+ const walletName = data.agentName
1380
+ ? data.agentName.toLowerCase().replace(/[^a-z0-9-_]/g, '-')
1381
+ : `tg-${data.wallet.slice(2, 8).toLowerCase()}`;
1382
+ // Check if wallet name already exists
1383
+ const existing = listWallets();
1384
+ let finalName = walletName;
1385
+ if (existing.some((w) => w.name === walletName)) {
1386
+ finalName = `${walletName}-${Date.now().toString(36).slice(-4)}`;
1387
+ }
1388
+ // Prompt for a local password (stronger than PIN, for CLI encryption)
1389
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
1390
+ const password = await new Promise((resolve) => {
1391
+ process.stderr.write(c.label(' Choose a password for local wallet encryption (8+ chars): '));
1392
+ rl2.question('', (answer) => {
1393
+ rl2.close();
1394
+ resolve(answer.trim());
1395
+ });
1396
+ });
1397
+ if (password.length < 8) {
1398
+ spinner.fail('Password must be at least 8 characters.');
1399
+ process.exit(1);
1400
+ }
1401
+ spinner.text = 'Importing wallet...';
1402
+ // Import with the CLI's wallet system (uses scrypt N=2^18)
1403
+ importFromPrivateKey(finalName, privateKey, password);
1404
+ setActiveWallet(finalName);
1405
+ // Save agent config if present
1406
+ const config = loadConfig();
1407
+ config.network = data.network || 'mainnet';
1408
+ if (!config.privateKey) {
1409
+ // Don't overwrite existing raw key, but set network
1410
+ }
1411
+ saveConfig(config);
1412
+ spinner.succeed('Connected!');
1413
+ if (opts.json) {
1414
+ console.log(JSON.stringify({
1415
+ wallet: data.wallet,
1416
+ walletName: finalName,
1417
+ agentName: data.agentName,
1418
+ hasApiKey: !!data.agentApiKey,
1419
+ network: data.network,
1420
+ }, null, 2));
1421
+ }
1422
+ else {
1423
+ console.log();
1424
+ console.log(kv('Wallet', c.value(data.wallet)));
1425
+ console.log(kv('Wallet name', c.accent(finalName)));
1426
+ console.log(kv('Network', networkBadge(data.network || 'mainnet')));
1427
+ if (data.agentName) {
1428
+ console.log(kv('Agent', c.value(data.agentName)));
1429
+ }
1430
+ if (data.agentApiKey) {
1431
+ console.log(kv('API Key', c.accent(data.agentApiKey)));
1432
+ console.log();
1433
+ console.log(c.warn(' Save your API key — you\'ll need it for verified deploys.'));
1434
+ }
1435
+ console.log();
1436
+ console.log(c.muted(' Your wallet is encrypted and stored locally.'));
1437
+ console.log(c.muted(' Next: clawncher deploy --verified --api-key <key>'));
1438
+ console.log();
1439
+ }
1440
+ }
1441
+ catch (err) {
1442
+ spinner.stop();
1443
+ handleError(err);
1444
+ }
1445
+ });
1446
+ // ============================================================================
1123
1447
  // Tokens Command (API)
1124
1448
  // ============================================================================
1125
1449
  program