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.
- package/README.md +41 -0
- package/dist/cli.js +446 -110
- 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.
|
|
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();
|