hufi-cli 0.6.1 → 0.8.1

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.
Files changed (3) hide show
  1. package/README.md +41 -0
  2. package/dist/cli.js +446 -110
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -56,6 +56,7 @@ hufi auth status
56
56
  | `campaign status` | Check join status |
57
57
  | `campaign progress` | Check your progress |
58
58
  | `campaign leaderboard` | View campaign leaderboard |
59
+ | `campaign create` | Create a new campaign (launch escrow on-chain) |
59
60
 
60
61
  ```bash
61
62
  hufi campaign list # list active campaigns
@@ -67,6 +68,33 @@ hufi campaign progress --address 0x... # your progress
67
68
  hufi campaign leaderboard --address 0x... # leaderboard
68
69
  ```
69
70
 
71
+ #### Campaign Create
72
+
73
+ Requires staked HMT, gas, and fund tokens (USDT/USDC). Creates an escrow contract on-chain.
74
+
75
+ ```bash
76
+ # Market Making
77
+ hufi campaign create \
78
+ --type market_making --exchange mexc --symbol HMT/USDT \
79
+ --start-date 2026-04-01 --end-date 2026-05-01 \
80
+ --fund-token USDT --fund-amount 10000 \
81
+ --daily-volume-target 50000
82
+
83
+ # Holding
84
+ hufi campaign create \
85
+ --type holding --exchange mexc --symbol HMT \
86
+ --start-date 2026-04-01 --end-date 2026-05-01 \
87
+ --fund-token USDT --fund-amount 5000 \
88
+ --daily-balance-target 1000
89
+
90
+ # Threshold
91
+ hufi campaign create \
92
+ --type threshold --exchange mexc --symbol HMT \
93
+ --start-date 2026-04-01 --end-date 2026-05-01 \
94
+ --fund-token USDT --fund-amount 5000 \
95
+ --minimum-balance-target 500
96
+ ```
97
+
70
98
  Running `campaign status/join/progress/leaderboard` without `-a` shows help.
71
99
 
72
100
  ### exchange
@@ -75,10 +103,14 @@ Running `campaign status/join/progress/leaderboard` without `-a` shows help.
75
103
  |---------|-------------|
76
104
  | `exchange register` | Register a read-only exchange API key |
77
105
  | `exchange list` | List registered API keys |
106
+ | `exchange delete` | Delete API keys for an exchange |
107
+ | `exchange revalidate` | Revalidate an exchange API key |
78
108
 
79
109
  ```bash
80
110
  hufi exchange register --name mexc --api-key <key> --secret-key <secret>
81
111
  hufi exchange list
112
+ hufi exchange revalidate --name mexc
113
+ hufi exchange delete --name mexc
82
114
  ```
83
115
 
84
116
  ### staking
@@ -102,6 +134,15 @@ hufi staking withdraw # withdraw unlocked token
102
134
 
103
135
  Supports Polygon (chain 137) and Ethereum (chain 1). Staking contract: `0x01D1...07F1D` on Polygon.
104
136
 
137
+ ### dashboard
138
+
139
+ Portfolio overview — staking, active campaigns, and progress in one view.
140
+
141
+ ```bash
142
+ hufi dashboard # full overview
143
+ hufi dashboard --json # machine output
144
+ ```
145
+
105
146
  ## Global Options
106
147
 
107
148
  | Option | Description |
package/dist/cli.js CHANGED
@@ -22958,6 +22958,18 @@ async function listExchangeApiKeys(baseUrl, accessToken) {
22958
22958
  headers: authHeaders(accessToken)
22959
22959
  });
22960
22960
  }
22961
+ async function deleteExchangeApiKey(baseUrl, accessToken, exchangeName) {
22962
+ await requestJson(`${baseUrl}/exchange-api-keys/${exchangeName}`, {
22963
+ method: "DELETE",
22964
+ headers: authHeaders(accessToken)
22965
+ });
22966
+ }
22967
+ async function revalidateExchangeApiKey(baseUrl, accessToken, exchangeName) {
22968
+ return await requestJson(`${baseUrl}/exchange-api-keys/${exchangeName}/revalidate`, {
22969
+ method: "POST",
22970
+ headers: authHeaders(accessToken)
22971
+ });
22972
+ }
22961
22973
 
22962
22974
  // src/commands/exchange.ts
22963
22975
  function requireAuth() {
@@ -23013,6 +23025,40 @@ function createExchangeCommand() {
23013
23025
  process.exitCode = 1;
23014
23026
  }
23015
23027
  });
23028
+ exchange.command("delete").description("Delete API keys for an exchange").requiredOption("-n, --name <name>", "Exchange name (e.g. mexc, bybit)").option("--json", "Output as JSON").action(async (opts) => {
23029
+ const { baseUrl, accessToken } = requireAuth();
23030
+ try {
23031
+ await deleteExchangeApiKey(baseUrl, accessToken, opts.name);
23032
+ if (opts.json) {
23033
+ printJson({ deleted: true, exchange_name: opts.name });
23034
+ } else {
23035
+ printText(`Deleted API keys for ${opts.name}.`);
23036
+ }
23037
+ } catch (err) {
23038
+ const message = err instanceof Error ? err.message : String(err);
23039
+ printText(`Failed to delete exchange API keys: ${message}`);
23040
+ process.exitCode = 1;
23041
+ }
23042
+ });
23043
+ exchange.command("revalidate").description("Revalidate exchange API key").requiredOption("-n, --name <name>", "Exchange name (e.g. mexc, bybit)").option("--json", "Output as JSON").action(async (opts) => {
23044
+ const { baseUrl, accessToken } = requireAuth();
23045
+ try {
23046
+ const result = await revalidateExchangeApiKey(baseUrl, accessToken, opts.name);
23047
+ if (opts.json) {
23048
+ printJson(result);
23049
+ } else {
23050
+ const status = result.is_valid ? "valid" : "invalid";
23051
+ printText(`${opts.name}: ${status}`);
23052
+ if (result.missing_permissions.length > 0) {
23053
+ printText(`Missing permissions: ${result.missing_permissions.join(", ")}`);
23054
+ }
23055
+ }
23056
+ } catch (err) {
23057
+ const message = err instanceof Error ? err.message : String(err);
23058
+ printText(`Failed to revalidate exchange API key: ${message}`);
23059
+ process.exitCode = 1;
23060
+ }
23061
+ });
23016
23062
  return exchange;
23017
23063
  }
23018
23064
 
@@ -24491,6 +24537,159 @@ async function getLeaderboard(baseUrl, chainId, campaignAddress, rankBy, limit =
24491
24537
  return await requestJson(`${baseUrl}/campaigns/${chainId}-${campaignAddress}/leaderboard?rankBy=${rankBy}&limit=${limit}`);
24492
24538
  }
24493
24539
 
24540
+ // src/services/campaign-create.ts
24541
+ import { createHash as createHash2 } from "node:crypto";
24542
+
24543
+ // src/lib/contracts.ts
24544
+ var HUMAN_PROTOCOL_CONTRACTS = {
24545
+ 137: {
24546
+ hmt: "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571BF",
24547
+ staking: "0x01D115E9E8bF0C58318793624CC662a030D07F1D",
24548
+ escrowFactory: "0x8D50dA7abe354a628a63ADfE23C19a2944612b83"
24549
+ },
24550
+ 1: {
24551
+ hmt: "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867",
24552
+ staking: "0x86Af9f6Cd34B69Db1B202223C6d6D109f2491569",
24553
+ escrowFactory: "0xE24e5C08E28331D24758b69A5E9f383D2bDD1c98"
24554
+ }
24555
+ };
24556
+ var RPC_URLS = {
24557
+ 137: [
24558
+ "https://polygon.drpc.org",
24559
+ "https://rpc.ankr.com/polygon",
24560
+ "https://polygon-mainnet.public.blastapi.io"
24561
+ ],
24562
+ 1: [
24563
+ "https://eth.drpc.org",
24564
+ "https://rpc.ankr.com/eth",
24565
+ "https://ethereum-rpc.publicnode.com"
24566
+ ]
24567
+ };
24568
+ var ORACLES = {
24569
+ exchangeOracle: "0x5b74d007ea08217bcde942a2132df43d568a6dca",
24570
+ recordingOracle: "0x3a2292c684e289fe5f07737b598fe0027ead5a0e",
24571
+ reputationOracle: "0x1519964f5cd2d9ef162b2b1b66f33669cca065c8"
24572
+ };
24573
+ var FUND_TOKENS = {
24574
+ 137: {
24575
+ USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
24576
+ USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
24577
+ },
24578
+ 1: {
24579
+ USDT: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
24580
+ USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
24581
+ }
24582
+ };
24583
+ var ESCROW_FACTORY_ABI = [
24584
+ "function createFundAndSetupEscrow(address token, uint256 amount, string jobRequesterId, address reputationOracle, address recordingOracle, address exchangeOracle, string manifest, string manifestHash) returns (address)",
24585
+ "event LaunchedV2(address indexed escrow, address indexed launcher, string jobRequesterId)"
24586
+ ];
24587
+ var ERC20_ABI = [
24588
+ "function balanceOf(address) external view returns (uint256)",
24589
+ "function allowance(address owner, address spender) external view returns (uint256)",
24590
+ "function approve(address spender, uint256 amount) external returns (bool)",
24591
+ "function decimals() external view returns (uint8)"
24592
+ ];
24593
+ function getContracts(chainId) {
24594
+ const c = HUMAN_PROTOCOL_CONTRACTS[chainId];
24595
+ if (!c) {
24596
+ throw new Error(`Unsupported chain ID: ${chainId}. Supported: ${Object.keys(HUMAN_PROTOCOL_CONTRACTS).join(", ")}`);
24597
+ }
24598
+ return c;
24599
+ }
24600
+ function getRpc(chainId) {
24601
+ const rpcs = RPC_URLS[chainId];
24602
+ if (!rpcs) {
24603
+ throw new Error(`No RPC URLs for chain ${chainId}`);
24604
+ }
24605
+ return rpcs[0];
24606
+ }
24607
+ function getFundTokenAddress(chainId, symbol) {
24608
+ const tokens = FUND_TOKENS[chainId];
24609
+ if (!tokens) {
24610
+ throw new Error(`No fund tokens for chain ${chainId}`);
24611
+ }
24612
+ const addr = tokens[symbol.toUpperCase()];
24613
+ if (!addr) {
24614
+ throw new Error(`Unknown fund token: ${symbol}. Supported: ${Object.keys(tokens).join(", ")}`);
24615
+ }
24616
+ return addr;
24617
+ }
24618
+
24619
+ // src/services/campaign-create.ts
24620
+ function getProvider2(chainId) {
24621
+ return new JsonRpcProvider(getRpc(chainId), chainId, { staticNetwork: true, batchMaxCount: 1 });
24622
+ }
24623
+ function buildManifest(params) {
24624
+ const base = {
24625
+ exchange: params.exchange,
24626
+ start_date: params.startDate,
24627
+ end_date: params.endDate
24628
+ };
24629
+ switch (params.type) {
24630
+ case "MARKET_MAKING":
24631
+ return JSON.stringify({
24632
+ ...base,
24633
+ type: params.type,
24634
+ pair: params.pair,
24635
+ daily_volume_target: params.dailyVolumeTarget
24636
+ });
24637
+ case "HOLDING":
24638
+ return JSON.stringify({
24639
+ ...base,
24640
+ type: params.type,
24641
+ symbol: params.symbol,
24642
+ daily_balance_target: params.dailyBalanceTarget
24643
+ });
24644
+ case "THRESHOLD":
24645
+ return JSON.stringify({
24646
+ ...base,
24647
+ type: params.type,
24648
+ symbol: params.symbol,
24649
+ minimum_balance_target: params.minimumBalanceTarget
24650
+ });
24651
+ default:
24652
+ throw new Error(`Unknown campaign type: ${params.type}`);
24653
+ }
24654
+ }
24655
+ function hashManifest(manifest) {
24656
+ return createHash2("sha1").update(manifest).digest("hex");
24657
+ }
24658
+ async function createCampaign(privateKey, chainId, params) {
24659
+ const contracts = getContracts(chainId);
24660
+ const provider = getProvider2(chainId);
24661
+ const wallet = new Wallet(privateKey, provider);
24662
+ const tokenAddress = getFundTokenAddress(chainId, params.fundToken);
24663
+ const hmtContract = new Contract(tokenAddress, ERC20_ABI, wallet);
24664
+ const decimals = await hmtContract.getFunction("decimals")();
24665
+ const fundAmountWei = parseUnits(params.fundAmount, decimals);
24666
+ const allowance = await hmtContract.getFunction("allowance")(wallet.address, contracts.escrowFactory);
24667
+ if (allowance < fundAmountWei) {
24668
+ const approveTx = await hmtContract.getFunction("approve")(contracts.escrowFactory, fundAmountWei);
24669
+ await approveTx.wait();
24670
+ }
24671
+ const manifest = buildManifest(params);
24672
+ const manifestHash = hashManifest(manifest);
24673
+ const factory = new Contract(contracts.escrowFactory, ESCROW_FACTORY_ABI, wallet);
24674
+ const tx = await factory.getFunction("createFundAndSetupEscrow")(tokenAddress, fundAmountWei, "hufi-campaign-launcher", ORACLES.reputationOracle, ORACLES.recordingOracle, ORACLES.exchangeOracle, manifest, manifestHash);
24675
+ const receipt = await tx.wait();
24676
+ const iface = factory.interface;
24677
+ let escrowAddress = "";
24678
+ for (const log of receipt.logs) {
24679
+ try {
24680
+ const parsed = iface.parseLog({ topics: log.topics, data: log.data });
24681
+ if (parsed?.name === "LaunchedV2") {
24682
+ escrowAddress = parsed.args.escrow;
24683
+ break;
24684
+ }
24685
+ } catch {}
24686
+ }
24687
+ return {
24688
+ escrowAddress: escrowAddress || "unknown",
24689
+ txHash: receipt.hash
24690
+ };
24691
+ }
24692
+
24494
24693
  // src/services/launcher/campaign.ts
24495
24694
  async function listLauncherCampaigns(baseUrl, chainId, limit = 20, status = "active") {
24496
24695
  const url = `${baseUrl}/campaigns?chain_id=${chainId}&status=${status}&limit=${limit}`;
@@ -24501,6 +24700,79 @@ async function getLauncherCampaign(baseUrl, chainId, campaignAddress) {
24501
24700
  return await requestJson(url);
24502
24701
  }
24503
24702
 
24703
+ // src/services/staking.ts
24704
+ var STAKING_ABI = [
24705
+ "function getAvailableStake(address _staker) external view returns (uint256)",
24706
+ "function getStakedTokens(address _staker) external view returns (uint256)",
24707
+ "function minimumStake() external view returns (uint256)",
24708
+ "function lockPeriod() external view returns (uint32)",
24709
+ "function stake(uint256 _tokens) external",
24710
+ "function unstake(uint256 _tokens) external",
24711
+ "function withdraw() external"
24712
+ ];
24713
+ function getProvider3(chainId) {
24714
+ return new JsonRpcProvider(getRpc(chainId), chainId, { staticNetwork: true, batchMaxCount: 1 });
24715
+ }
24716
+ async function getStakingInfo(address, chainId) {
24717
+ const contracts = getContracts(chainId);
24718
+ const provider = getProvider3(chainId);
24719
+ const stakingContract = new Contract(contracts.staking, STAKING_ABI, provider);
24720
+ const hmtContract = new Contract(contracts.hmt, ERC20_ABI, provider);
24721
+ const [stakedTokens, availableStake, minimumStake, lockPeriod, hmtBalance] = await Promise.all([
24722
+ stakingContract.getFunction("getStakedTokens")(address),
24723
+ stakingContract.getFunction("getAvailableStake")(address),
24724
+ stakingContract.getFunction("minimumStake")(),
24725
+ stakingContract.getFunction("lockPeriod")(),
24726
+ hmtContract.getFunction("balanceOf")(address)
24727
+ ]);
24728
+ const lockedTokens = stakedTokens > availableStake ? stakedTokens - availableStake : 0n;
24729
+ return {
24730
+ stakedTokens: formatUnits(stakedTokens, 18),
24731
+ availableStake: formatUnits(availableStake, 18),
24732
+ lockedTokens: formatUnits(lockedTokens, 18),
24733
+ unlockBlock: 0,
24734
+ minimumStake: formatUnits(minimumStake, 18),
24735
+ lockPeriod: Number(lockPeriod),
24736
+ hmtBalance: formatUnits(hmtBalance, 18),
24737
+ chainId
24738
+ };
24739
+ }
24740
+ async function stakeHMT(privateKey, amount, chainId) {
24741
+ const contracts = getContracts(chainId);
24742
+ const provider = getProvider3(chainId);
24743
+ const wallet = new Wallet(privateKey, provider);
24744
+ const stakingContract = new Contract(contracts.staking, STAKING_ABI, wallet);
24745
+ const hmtContract = new Contract(contracts.hmt, ERC20_ABI, wallet);
24746
+ const amountWei = parseUnits(amount, 18);
24747
+ const allowance = await hmtContract.getFunction("allowance")(wallet.address, contracts.staking);
24748
+ if (allowance < amountWei) {
24749
+ const approveTx = await hmtContract.getFunction("approve")(contracts.staking, amountWei);
24750
+ await approveTx.wait();
24751
+ }
24752
+ const tx = await stakingContract.getFunction("stake")(amountWei);
24753
+ const receipt = await tx.wait();
24754
+ return receipt.hash;
24755
+ }
24756
+ async function unstakeHMT(privateKey, amount, chainId) {
24757
+ const contracts = getContracts(chainId);
24758
+ const provider = getProvider3(chainId);
24759
+ const wallet = new Wallet(privateKey, provider);
24760
+ const stakingContract = new Contract(contracts.staking, STAKING_ABI, wallet);
24761
+ const amountWei = parseUnits(amount, 18);
24762
+ const tx = await stakingContract.getFunction("unstake")(amountWei);
24763
+ const receipt = await tx.wait();
24764
+ return receipt.hash;
24765
+ }
24766
+ async function withdrawHMT(privateKey, chainId) {
24767
+ const contracts = getContracts(chainId);
24768
+ const provider = getProvider3(chainId);
24769
+ const wallet = new Wallet(privateKey, provider);
24770
+ const stakingContract = new Contract(contracts.staking, STAKING_ABI, wallet);
24771
+ const tx = await stakingContract.getFunction("withdraw")();
24772
+ const receipt = await tx.wait();
24773
+ return receipt.hash;
24774
+ }
24775
+
24504
24776
  // src/commands/campaign.ts
24505
24777
  function requireAuth2() {
24506
24778
  const config = loadConfig();
@@ -24731,120 +25003,103 @@ function createCampaignCommand() {
24731
25003
  process.exitCode = 1;
24732
25004
  }
24733
25005
  });
25006
+ const VALID_TYPES = ["market_making", "holding", "threshold"];
25007
+ campaign.command("create").description("Create a new campaign (launch escrow on-chain)").requiredOption("--type <type>", `Campaign type (${VALID_TYPES.join(", ")})`).requiredOption("--exchange <name>", "Exchange name (e.g. mexc, bybit)").requiredOption("--symbol <symbol>", "Token symbol or pair (e.g. HMT/USDT or HMT)").requiredOption("--start-date <date>", "Start date (YYYY-MM-DD)").requiredOption("--end-date <date>", "End date (YYYY-MM-DD)").requiredOption("--fund-token <token>", "Fund token (USDT or USDC)").requiredOption("--fund-amount <amount>", "Fund amount").option("--daily-volume-target <n>", "Daily volume target (market_making)", Number).option("--daily-balance-target <n>", "Daily balance target (holding)", Number).option("--minimum-balance-target <n>", "Minimum balance target (threshold)", Number).option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("--json", "Output as JSON").action(async (opts) => {
25008
+ const privateKey = loadKey();
25009
+ if (!privateKey) {
25010
+ printText("No private key found. Run: hufi auth generate");
25011
+ process.exit(1);
25012
+ }
25013
+ const config = loadConfig();
25014
+ const address = config.address;
25015
+ if (!address) {
25016
+ printText("Not authenticated. Run: hufi auth login --private-key <key>");
25017
+ process.exit(1);
25018
+ }
25019
+ const type = opts.type.toLowerCase();
25020
+ if (!VALID_TYPES.includes(type)) {
25021
+ printText(`Invalid type: ${opts.type}. Must be one of: ${VALID_TYPES.join(", ")}`);
25022
+ process.exit(1);
25023
+ }
25024
+ const typeMap = {
25025
+ market_making: "MARKET_MAKING",
25026
+ holding: "HOLDING",
25027
+ threshold: "THRESHOLD"
25028
+ };
25029
+ const startDate = new Date(opts.startDate);
25030
+ const endDate = new Date(opts.endDate);
25031
+ const durationHours = (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60);
25032
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
25033
+ printText("Invalid date format. Use YYYY-MM-DD.");
25034
+ process.exit(1);
25035
+ }
25036
+ if (durationHours < 6) {
25037
+ printText("Campaign duration must be at least 6 hours.");
25038
+ process.exit(1);
25039
+ }
25040
+ if (durationHours > 100 * 24) {
25041
+ printText("Campaign duration must be at most 100 days.");
25042
+ process.exit(1);
25043
+ }
25044
+ if (type === "market_making" && !opts.dailyVolumeTarget) {
25045
+ printText("--daily-volume-target is required for market_making campaigns.");
25046
+ process.exit(1);
25047
+ }
25048
+ if (type === "holding" && !opts.dailyBalanceTarget) {
25049
+ printText("--daily-balance-target is required for holding campaigns.");
25050
+ process.exit(1);
25051
+ }
25052
+ if (type === "threshold" && !opts.minimumBalanceTarget) {
25053
+ printText("--minimum-balance-target is required for threshold campaigns.");
25054
+ process.exit(1);
25055
+ }
25056
+ try {
25057
+ const stakingInfo = await getStakingInfo(address, opts.chainId);
25058
+ if (Number(stakingInfo.stakedTokens) <= 0) {
25059
+ printText("You must stake HMT before creating a campaign.");
25060
+ printText("Run: hufi staking stake -a <amount>");
25061
+ process.exit(1);
25062
+ }
25063
+ } catch {
25064
+ printText("Warning: could not verify staking status.");
25065
+ }
25066
+ const params = {
25067
+ type: typeMap[type],
25068
+ exchange: opts.exchange,
25069
+ pair: type === "market_making" ? opts.symbol : undefined,
25070
+ symbol: type !== "market_making" ? opts.symbol : undefined,
25071
+ startDate: startDate.toISOString(),
25072
+ endDate: endDate.toISOString(),
25073
+ fundToken: opts.fundToken.toUpperCase(),
25074
+ fundAmount: opts.fundAmount,
25075
+ dailyVolumeTarget: opts.dailyVolumeTarget,
25076
+ dailyBalanceTarget: opts.dailyBalanceTarget,
25077
+ minimumBalanceTarget: opts.minimumBalanceTarget
25078
+ };
25079
+ try {
25080
+ printText(`Creating ${type} campaign on ${opts.exchange}...`);
25081
+ printText(` Fund: ${opts.fundAmount} ${opts.fundToken}`);
25082
+ printText(` Duration: ${opts.startDate} ~ ${opts.endDate}`);
25083
+ printText("");
25084
+ const result = await createCampaign(privateKey, opts.chainId, params);
25085
+ if (opts.json) {
25086
+ printJson(result);
25087
+ } else {
25088
+ printText(`Campaign created successfully!`);
25089
+ printText(` Escrow: ${result.escrowAddress}`);
25090
+ printText(` TX: ${result.txHash}`);
25091
+ }
25092
+ } catch (err) {
25093
+ const message = err instanceof Error ? err.message : String(err);
25094
+ printText(`Failed to create campaign: ${message}`);
25095
+ process.exitCode = 1;
25096
+ }
25097
+ });
24734
25098
  return campaign;
24735
25099
  }
24736
25100
 
24737
25101
  // src/commands/staking.ts
24738
25102
  var import_qrcode = __toESM(require_server(), 1);
24739
-
24740
- // src/services/staking.ts
24741
- var STAKING_CONTRACTS = {
24742
- 137: {
24743
- staking: "0x01D115E9E8bF0C58318793624CC662a030D07F1D",
24744
- hmt: "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571BF",
24745
- rpcs: [
24746
- "https://polygon.drpc.org",
24747
- "https://rpc.ankr.com/polygon",
24748
- "https://polygon-mainnet.public.blastapi.io"
24749
- ]
24750
- },
24751
- 1: {
24752
- staking: "0x86Af9f6Cd34B69Db1B202223C6d6D109f2491569",
24753
- hmt: "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867",
24754
- rpcs: [
24755
- "https://eth.drpc.org",
24756
- "https://rpc.ankr.com/eth",
24757
- "https://ethereum-rpc.publicnode.com"
24758
- ]
24759
- }
24760
- };
24761
- var STAKING_ABI = [
24762
- "function getAvailableStake(address _staker) external view returns (uint256)",
24763
- "function getStakedTokens(address _staker) external view returns (uint256)",
24764
- "function minimumStake() external view returns (uint256)",
24765
- "function lockPeriod() external view returns (uint32)",
24766
- "function stake(uint256 _tokens) external",
24767
- "function unstake(uint256 _tokens) external",
24768
- "function withdraw() external"
24769
- ];
24770
- var ERC20_ABI = [
24771
- "function balanceOf(address) external view returns (uint256)",
24772
- "function allowance(address owner, address spender) external view returns (uint256)",
24773
- "function approve(address spender, uint256 amount) external returns (bool)",
24774
- "function decimals() external view returns (uint8)"
24775
- ];
24776
- function getProvider2(chainId) {
24777
- const config = getConfig(chainId);
24778
- return new JsonRpcProvider(config.rpcs[0], chainId, { staticNetwork: true, batchMaxCount: 1 });
24779
- }
24780
- function getConfig(chainId) {
24781
- const config = STAKING_CONTRACTS[chainId];
24782
- if (!config) {
24783
- throw new Error(`Unsupported chain ID: ${chainId}. Supported: ${Object.keys(STAKING_CONTRACTS).join(", ")}`);
24784
- }
24785
- return config;
24786
- }
24787
- async function getStakingInfo(address, chainId) {
24788
- const config = getConfig(chainId);
24789
- const provider = getProvider2(chainId);
24790
- const stakingContract = new Contract(config.staking, STAKING_ABI, provider);
24791
- const hmtContract = new Contract(config.hmt, ERC20_ABI, provider);
24792
- const [stakedTokens, availableStake, minimumStake, lockPeriod, hmtBalance] = await Promise.all([
24793
- stakingContract.getFunction("getStakedTokens")(address),
24794
- stakingContract.getFunction("getAvailableStake")(address),
24795
- stakingContract.getFunction("minimumStake")(),
24796
- stakingContract.getFunction("lockPeriod")(),
24797
- hmtContract.getFunction("balanceOf")(address)
24798
- ]);
24799
- const lockedTokens = stakedTokens > availableStake ? stakedTokens - availableStake : 0n;
24800
- return {
24801
- stakedTokens: formatUnits(stakedTokens, 18),
24802
- availableStake: formatUnits(availableStake, 18),
24803
- lockedTokens: formatUnits(lockedTokens, 18),
24804
- unlockBlock: 0,
24805
- minimumStake: formatUnits(minimumStake, 18),
24806
- lockPeriod: Number(lockPeriod),
24807
- hmtBalance: formatUnits(hmtBalance, 18),
24808
- chainId
24809
- };
24810
- }
24811
- async function stakeHMT(privateKey, amount, chainId) {
24812
- const config = getConfig(chainId);
24813
- const provider = getProvider2(chainId);
24814
- const wallet = new Wallet(privateKey, provider);
24815
- const stakingContract = new Contract(config.staking, STAKING_ABI, wallet);
24816
- const hmtContract = new Contract(config.hmt, ERC20_ABI, wallet);
24817
- const amountWei = parseUnits(amount, 18);
24818
- const allowance = await hmtContract.getFunction("allowance")(wallet.address, config.staking);
24819
- if (allowance < amountWei) {
24820
- const approveTx = await hmtContract.getFunction("approve")(config.staking, amountWei);
24821
- await approveTx.wait();
24822
- }
24823
- const tx = await stakingContract.getFunction("stake")(amountWei);
24824
- const receipt = await tx.wait();
24825
- return receipt.hash;
24826
- }
24827
- async function unstakeHMT(privateKey, amount, chainId) {
24828
- const config = getConfig(chainId);
24829
- const provider = getProvider2(chainId);
24830
- const wallet = new Wallet(privateKey, provider);
24831
- const stakingContract = new Contract(config.staking, STAKING_ABI, wallet);
24832
- const amountWei = parseUnits(amount, 18);
24833
- const tx = await stakingContract.getFunction("unstake")(amountWei);
24834
- const receipt = await tx.wait();
24835
- return receipt.hash;
24836
- }
24837
- async function withdrawHMT(privateKey, chainId) {
24838
- const config = getConfig(chainId);
24839
- const provider = getProvider2(chainId);
24840
- const wallet = new Wallet(privateKey, provider);
24841
- const stakingContract = new Contract(config.staking, STAKING_ABI, wallet);
24842
- const tx = await stakingContract.getFunction("withdraw")();
24843
- const receipt = await tx.wait();
24844
- return receipt.hash;
24845
- }
24846
-
24847
- // src/commands/staking.ts
24848
25103
  function requireKey() {
24849
25104
  const key = loadKey();
24850
25105
  if (!key) {
@@ -24959,9 +25214,89 @@ Send HMT to this address on Polygon (chain 137) or Ethereum (chain 1).`);
24959
25214
  return staking;
24960
25215
  }
24961
25216
 
25217
+ // src/commands/dashboard.ts
25218
+ function requireAuth3() {
25219
+ const config = loadConfig();
25220
+ if (!config.accessToken || !config.address) {
25221
+ printText("Not authenticated. Run: hufi auth login --private-key <key>");
25222
+ process.exit(1);
25223
+ }
25224
+ return {
25225
+ baseUrl: config.recordingApiUrl.replace(/\/+$/, ""),
25226
+ accessToken: config.accessToken,
25227
+ address: config.address
25228
+ };
25229
+ }
25230
+ function createDashboardCommand() {
25231
+ const dashboard = new Command("dashboard").description("Portfolio overview — staking, campaigns, and earnings").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("--json", "Output as JSON").action(async (opts) => {
25232
+ const { baseUrl, accessToken, address } = requireAuth3();
25233
+ try {
25234
+ const [stakingInfo, campaignsResult] = await Promise.all([
25235
+ getStakingInfo(address, opts.chainId).catch(() => null),
25236
+ listJoinedCampaigns(baseUrl, accessToken, 50).catch(() => null)
25237
+ ]);
25238
+ const campaigns = campaignsResult?.results ?? [];
25239
+ const activeCampaigns = campaigns.filter((c) => {
25240
+ const r = c;
25241
+ return r.status === "active" || r.status === "ACTIVE";
25242
+ });
25243
+ const progressPromises = activeCampaigns.map(async (c) => {
25244
+ const r = c;
25245
+ const chainId = r.chain_id ?? opts.chainId;
25246
+ const campaignAddress = r.address ?? r.escrow_address ?? "";
25247
+ if (!campaignAddress)
25248
+ return { campaign: c, progress: null };
25249
+ try {
25250
+ const progress = await getMyProgress(baseUrl, accessToken, chainId, campaignAddress);
25251
+ return { campaign: c, progress };
25252
+ } catch {
25253
+ return { campaign: c, progress: null };
25254
+ }
25255
+ });
25256
+ const progressResults = await Promise.all(progressPromises);
25257
+ if (opts.json) {
25258
+ printJson({
25259
+ address,
25260
+ chainId: opts.chainId,
25261
+ staking: stakingInfo,
25262
+ activeCampaigns: progressResults
25263
+ });
25264
+ return;
25265
+ }
25266
+ printText(`Wallet: ${address} Chain: ${opts.chainId}
25267
+ `);
25268
+ if (stakingInfo) {
25269
+ printText("Staking");
25270
+ printText(` Staked: ${Number(stakingInfo.stakedTokens).toLocaleString()} HMT`);
25271
+ printText(` Available: ${Number(stakingInfo.availableStake).toLocaleString()} HMT`);
25272
+ printText(` Balance: ${Number(stakingInfo.hmtBalance).toLocaleString()} HMT`);
25273
+ printText("");
25274
+ }
25275
+ if (activeCampaigns.length === 0) {
25276
+ printText("No active campaigns.");
25277
+ } else {
25278
+ printText(`Active Campaigns (${activeCampaigns.length})`);
25279
+ for (const { campaign, progress } of progressResults) {
25280
+ const r = campaign;
25281
+ const exchange = r.exchange_name ?? "?";
25282
+ const symbol = r.symbol ?? "?";
25283
+ const type = r.type ?? "?";
25284
+ const score = progress && typeof progress === "object" && "my_score" in progress ? ` score: ${progress.my_score}` : "";
25285
+ printText(` ${exchange} ${symbol} (${type})${score}`);
25286
+ }
25287
+ }
25288
+ } catch (err) {
25289
+ const message = err instanceof Error ? err.message : String(err);
25290
+ printText(`Failed to load dashboard: ${message}`);
25291
+ process.exitCode = 1;
25292
+ }
25293
+ });
25294
+ return dashboard;
25295
+ }
25296
+
24962
25297
  // src/cli.ts
24963
25298
  var program2 = new Command;
24964
- program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("0.6.1").option("--config-file <path>", "Custom config file path (default: ~/.hufi-cli/config.json)").option("--key-file <path>", "Custom key file path (default: ~/.hufi-cli/key.json)").hook("preAction", (thisCommand) => {
25299
+ program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("0.8.1").option("--config-file <path>", "Custom config file path (default: ~/.hufi-cli/config.json)").option("--key-file <path>", "Custom key file path (default: ~/.hufi-cli/key.json)").hook("preAction", (thisCommand) => {
24965
25300
  const opts = thisCommand.opts();
24966
25301
  if (opts.configFile) {
24967
25302
  setConfigFile(opts.configFile);
@@ -24974,4 +25309,5 @@ program2.addCommand(createAuthCommand());
24974
25309
  program2.addCommand(createExchangeCommand());
24975
25310
  program2.addCommand(createCampaignCommand());
24976
25311
  program2.addCommand(createStakingCommand());
25312
+ program2.addCommand(createDashboardCommand());
24977
25313
  program2.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hufi-cli",
3
- "version": "0.6.1",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "hufi": "./dist/cli.js"