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 +325 -1
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|