hufi-cli 0.6.1 → 1.0.0
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 +45 -0
- package/dist/cli.js +805 -161
- 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
|
|
@@ -64,9 +65,38 @@ hufi campaign get --chain-id 137 --address 0x... # campaign details
|
|
|
64
65
|
hufi campaign join --address 0x... # join (chain-id defaults to 137)
|
|
65
66
|
hufi campaign status --address 0x... # check status
|
|
66
67
|
hufi campaign progress --address 0x... # your progress
|
|
68
|
+
hufi campaign progress --address 0x... --watch # live updates (polling)
|
|
69
|
+
hufi campaign progress --address 0x... --watch --interval 3000
|
|
67
70
|
hufi campaign leaderboard --address 0x... # leaderboard
|
|
68
71
|
```
|
|
69
72
|
|
|
73
|
+
#### Campaign Create
|
|
74
|
+
|
|
75
|
+
Requires staked HMT, gas, and fund tokens (USDT/USDC). Creates an escrow contract on-chain.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Market Making
|
|
79
|
+
hufi campaign create \
|
|
80
|
+
--type market_making --exchange mexc --symbol HMT/USDT \
|
|
81
|
+
--start-date 2026-04-01 --end-date 2026-05-01 \
|
|
82
|
+
--fund-token USDT --fund-amount 10000 \
|
|
83
|
+
--daily-volume-target 50000
|
|
84
|
+
|
|
85
|
+
# Holding
|
|
86
|
+
hufi campaign create \
|
|
87
|
+
--type holding --exchange mexc --symbol HMT \
|
|
88
|
+
--start-date 2026-04-01 --end-date 2026-05-01 \
|
|
89
|
+
--fund-token USDT --fund-amount 5000 \
|
|
90
|
+
--daily-balance-target 1000
|
|
91
|
+
|
|
92
|
+
# Threshold
|
|
93
|
+
hufi campaign create \
|
|
94
|
+
--type threshold --exchange mexc --symbol HMT \
|
|
95
|
+
--start-date 2026-04-01 --end-date 2026-05-01 \
|
|
96
|
+
--fund-token USDT --fund-amount 5000 \
|
|
97
|
+
--minimum-balance-target 500
|
|
98
|
+
```
|
|
99
|
+
|
|
70
100
|
Running `campaign status/join/progress/leaderboard` without `-a` shows help.
|
|
71
101
|
|
|
72
102
|
### exchange
|
|
@@ -75,10 +105,14 @@ Running `campaign status/join/progress/leaderboard` without `-a` shows help.
|
|
|
75
105
|
|---------|-------------|
|
|
76
106
|
| `exchange register` | Register a read-only exchange API key |
|
|
77
107
|
| `exchange list` | List registered API keys |
|
|
108
|
+
| `exchange delete` | Delete API keys for an exchange |
|
|
109
|
+
| `exchange revalidate` | Revalidate an exchange API key |
|
|
78
110
|
|
|
79
111
|
```bash
|
|
80
112
|
hufi exchange register --name mexc --api-key <key> --secret-key <secret>
|
|
81
113
|
hufi exchange list
|
|
114
|
+
hufi exchange revalidate --name mexc
|
|
115
|
+
hufi exchange delete --name mexc
|
|
82
116
|
```
|
|
83
117
|
|
|
84
118
|
### staking
|
|
@@ -102,6 +136,17 @@ hufi staking withdraw # withdraw unlocked token
|
|
|
102
136
|
|
|
103
137
|
Supports Polygon (chain 137) and Ethereum (chain 1). Staking contract: `0x01D1...07F1D` on Polygon.
|
|
104
138
|
|
|
139
|
+
### dashboard
|
|
140
|
+
|
|
141
|
+
Portfolio overview — staking, active campaigns, and progress in one view.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
hufi dashboard # full overview
|
|
145
|
+
hufi dashboard --json # machine output
|
|
146
|
+
hufi dashboard --export csv # export active campaign rows as CSV
|
|
147
|
+
hufi dashboard --export json
|
|
148
|
+
```
|
|
149
|
+
|
|
105
150
|
## Global Options
|
|
106
151
|
|
|
107
152
|
| Option | Description |
|
package/dist/cli.js
CHANGED
|
@@ -22697,28 +22697,62 @@ class ApiError extends Error {
|
|
|
22697
22697
|
}
|
|
22698
22698
|
|
|
22699
22699
|
// src/lib/http.ts
|
|
22700
|
+
function sleep(ms) {
|
|
22701
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22702
|
+
}
|
|
22703
|
+
function isRetryableStatus(status) {
|
|
22704
|
+
return status === 408 || status === 429 || status >= 500;
|
|
22705
|
+
}
|
|
22706
|
+
function resolveMessage(payload, status) {
|
|
22707
|
+
let message = `HTTP ${status}`;
|
|
22708
|
+
if (payload && typeof payload === "object" && "message" in payload) {
|
|
22709
|
+
message = String(payload.message);
|
|
22710
|
+
} else if (typeof payload === "string" && payload) {
|
|
22711
|
+
message = payload;
|
|
22712
|
+
}
|
|
22713
|
+
return message;
|
|
22714
|
+
}
|
|
22700
22715
|
async function requestJson(url, options = {}) {
|
|
22701
|
-
const
|
|
22702
|
-
|
|
22703
|
-
|
|
22704
|
-
|
|
22705
|
-
|
|
22706
|
-
|
|
22707
|
-
|
|
22708
|
-
|
|
22709
|
-
|
|
22710
|
-
|
|
22711
|
-
|
|
22712
|
-
|
|
22713
|
-
|
|
22714
|
-
|
|
22715
|
-
|
|
22716
|
-
|
|
22717
|
-
|
|
22716
|
+
const retries = options.retry?.retries ?? 2;
|
|
22717
|
+
const initialDelayMs = options.retry?.initialDelayMs ?? 250;
|
|
22718
|
+
const maxDelayMs = options.retry?.maxDelayMs ?? 3000;
|
|
22719
|
+
let attempt = 0;
|
|
22720
|
+
while (true) {
|
|
22721
|
+
try {
|
|
22722
|
+
const response = await fetch(url, {
|
|
22723
|
+
method: options.method ?? "GET",
|
|
22724
|
+
headers: {
|
|
22725
|
+
"Content-Type": "application/json",
|
|
22726
|
+
...options.headers ?? {}
|
|
22727
|
+
},
|
|
22728
|
+
body: options.body
|
|
22729
|
+
});
|
|
22730
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
22731
|
+
const isJson = contentType.includes("application/json");
|
|
22732
|
+
const payload = isJson ? await response.json() : await response.text();
|
|
22733
|
+
if (!response.ok) {
|
|
22734
|
+
const message = resolveMessage(payload, response.status);
|
|
22735
|
+
if (isRetryableStatus(response.status) && attempt < retries) {
|
|
22736
|
+
const delay = Math.min(initialDelayMs * 2 ** attempt, maxDelayMs);
|
|
22737
|
+
attempt += 1;
|
|
22738
|
+
await sleep(delay);
|
|
22739
|
+
continue;
|
|
22740
|
+
}
|
|
22741
|
+
throw new ApiError(message, response.status, payload);
|
|
22742
|
+
}
|
|
22743
|
+
return payload;
|
|
22744
|
+
} catch (err) {
|
|
22745
|
+
if (err instanceof ApiError) {
|
|
22746
|
+
throw err;
|
|
22747
|
+
}
|
|
22748
|
+
if (attempt >= retries) {
|
|
22749
|
+
throw err;
|
|
22750
|
+
}
|
|
22751
|
+
const delay = Math.min(initialDelayMs * 2 ** attempt, maxDelayMs);
|
|
22752
|
+
attempt += 1;
|
|
22753
|
+
await sleep(delay);
|
|
22718
22754
|
}
|
|
22719
|
-
throw new ApiError(message, response.status, payload);
|
|
22720
22755
|
}
|
|
22721
|
-
return payload;
|
|
22722
22756
|
}
|
|
22723
22757
|
function authHeaders(accessToken) {
|
|
22724
22758
|
return { Authorization: `Bearer ${accessToken}` };
|
|
@@ -22840,6 +22874,38 @@ function loadKey() {
|
|
|
22840
22874
|
return null;
|
|
22841
22875
|
}
|
|
22842
22876
|
}
|
|
22877
|
+
function isHttpUrl(value) {
|
|
22878
|
+
try {
|
|
22879
|
+
const parsed = new URL(value);
|
|
22880
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
22881
|
+
} catch {
|
|
22882
|
+
return false;
|
|
22883
|
+
}
|
|
22884
|
+
}
|
|
22885
|
+
function isEvmAddress(value) {
|
|
22886
|
+
return /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
22887
|
+
}
|
|
22888
|
+
function validateConfig(config) {
|
|
22889
|
+
const issues = [];
|
|
22890
|
+
if (config.recordingApiUrl !== undefined && !isHttpUrl(config.recordingApiUrl)) {
|
|
22891
|
+
issues.push("recordingApiUrl must be a valid http/https URL");
|
|
22892
|
+
}
|
|
22893
|
+
if (config.launcherApiUrl !== undefined && !isHttpUrl(config.launcherApiUrl)) {
|
|
22894
|
+
issues.push("launcherApiUrl must be a valid http/https URL");
|
|
22895
|
+
}
|
|
22896
|
+
if (config.defaultChainId !== undefined) {
|
|
22897
|
+
if (!Number.isInteger(config.defaultChainId) || config.defaultChainId <= 0) {
|
|
22898
|
+
issues.push("defaultChainId must be a positive integer");
|
|
22899
|
+
}
|
|
22900
|
+
}
|
|
22901
|
+
if (config.address !== undefined && !isEvmAddress(config.address)) {
|
|
22902
|
+
issues.push("address must be a valid 0x-prefixed EVM address");
|
|
22903
|
+
}
|
|
22904
|
+
return {
|
|
22905
|
+
valid: issues.length === 0,
|
|
22906
|
+
issues
|
|
22907
|
+
};
|
|
22908
|
+
}
|
|
22843
22909
|
|
|
22844
22910
|
// src/lib/output.ts
|
|
22845
22911
|
function printJson(data) {
|
|
@@ -22958,9 +23024,21 @@ async function listExchangeApiKeys(baseUrl, accessToken) {
|
|
|
22958
23024
|
headers: authHeaders(accessToken)
|
|
22959
23025
|
});
|
|
22960
23026
|
}
|
|
23027
|
+
async function deleteExchangeApiKey(baseUrl, accessToken, exchangeName) {
|
|
23028
|
+
await requestJson(`${baseUrl}/exchange-api-keys/${exchangeName}`, {
|
|
23029
|
+
method: "DELETE",
|
|
23030
|
+
headers: authHeaders(accessToken)
|
|
23031
|
+
});
|
|
23032
|
+
}
|
|
23033
|
+
async function revalidateExchangeApiKey(baseUrl, accessToken, exchangeName) {
|
|
23034
|
+
return await requestJson(`${baseUrl}/exchange-api-keys/${exchangeName}/revalidate`, {
|
|
23035
|
+
method: "POST",
|
|
23036
|
+
headers: authHeaders(accessToken)
|
|
23037
|
+
});
|
|
23038
|
+
}
|
|
22961
23039
|
|
|
22962
|
-
// src/
|
|
22963
|
-
function
|
|
23040
|
+
// src/lib/require-auth.ts
|
|
23041
|
+
function requireAuthToken() {
|
|
22964
23042
|
const config = loadConfig();
|
|
22965
23043
|
if (!config.accessToken) {
|
|
22966
23044
|
printText("Not authenticated. Run: hufi auth login --private-key <key>");
|
|
@@ -22971,10 +23049,24 @@ function requireAuth() {
|
|
|
22971
23049
|
accessToken: config.accessToken
|
|
22972
23050
|
};
|
|
22973
23051
|
}
|
|
23052
|
+
function requireAuthAddress() {
|
|
23053
|
+
const config = loadConfig();
|
|
23054
|
+
if (!config.accessToken || !config.address) {
|
|
23055
|
+
printText("Not authenticated. Run: hufi auth login --private-key <key>");
|
|
23056
|
+
process.exit(1);
|
|
23057
|
+
}
|
|
23058
|
+
return {
|
|
23059
|
+
baseUrl: config.recordingApiUrl.replace(/\/+$/, ""),
|
|
23060
|
+
accessToken: config.accessToken,
|
|
23061
|
+
address: config.address
|
|
23062
|
+
};
|
|
23063
|
+
}
|
|
23064
|
+
|
|
23065
|
+
// src/commands/exchange.ts
|
|
22974
23066
|
function createExchangeCommand() {
|
|
22975
23067
|
const exchange = new Command("exchange").description("Exchange API key management");
|
|
22976
23068
|
exchange.command("register").description("Register a read-only exchange API key").requiredOption("-n, --name <name>", "Exchange name (e.g. binance, mexc)").requiredOption("--api-key <key>", "Read-only API key").requiredOption("--secret-key <key>", "Read-only API secret").option("--bitmart-memo <memo>", "Bitmart memo (only for bitmart)").option("--json", "Output as JSON").action(async (opts) => {
|
|
22977
|
-
const { baseUrl, accessToken } =
|
|
23069
|
+
const { baseUrl, accessToken } = requireAuthToken();
|
|
22978
23070
|
try {
|
|
22979
23071
|
const result = await registerExchangeApiKey(baseUrl, accessToken, opts.name, opts.apiKey, opts.secretKey, opts.bitmartMemo);
|
|
22980
23072
|
if (opts.json) {
|
|
@@ -22992,7 +23084,7 @@ function createExchangeCommand() {
|
|
|
22992
23084
|
}
|
|
22993
23085
|
});
|
|
22994
23086
|
exchange.command("list").description("List registered exchange API keys").option("--json", "Output as JSON").action(async (opts) => {
|
|
22995
|
-
const { baseUrl, accessToken } =
|
|
23087
|
+
const { baseUrl, accessToken } = requireAuthToken();
|
|
22996
23088
|
try {
|
|
22997
23089
|
const keys = await listExchangeApiKeys(baseUrl, accessToken);
|
|
22998
23090
|
if (opts.json) {
|
|
@@ -23013,6 +23105,40 @@ function createExchangeCommand() {
|
|
|
23013
23105
|
process.exitCode = 1;
|
|
23014
23106
|
}
|
|
23015
23107
|
});
|
|
23108
|
+
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) => {
|
|
23109
|
+
const { baseUrl, accessToken } = requireAuthToken();
|
|
23110
|
+
try {
|
|
23111
|
+
await deleteExchangeApiKey(baseUrl, accessToken, opts.name);
|
|
23112
|
+
if (opts.json) {
|
|
23113
|
+
printJson({ deleted: true, exchange_name: opts.name });
|
|
23114
|
+
} else {
|
|
23115
|
+
printText(`Deleted API keys for ${opts.name}.`);
|
|
23116
|
+
}
|
|
23117
|
+
} catch (err) {
|
|
23118
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23119
|
+
printText(`Failed to delete exchange API keys: ${message}`);
|
|
23120
|
+
process.exitCode = 1;
|
|
23121
|
+
}
|
|
23122
|
+
});
|
|
23123
|
+
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) => {
|
|
23124
|
+
const { baseUrl, accessToken } = requireAuthToken();
|
|
23125
|
+
try {
|
|
23126
|
+
const result = await revalidateExchangeApiKey(baseUrl, accessToken, opts.name);
|
|
23127
|
+
if (opts.json) {
|
|
23128
|
+
printJson(result);
|
|
23129
|
+
} else {
|
|
23130
|
+
const status = result.is_valid ? "valid" : "invalid";
|
|
23131
|
+
printText(`${opts.name}: ${status}`);
|
|
23132
|
+
if (result.missing_permissions.length > 0) {
|
|
23133
|
+
printText(`Missing permissions: ${result.missing_permissions.join(", ")}`);
|
|
23134
|
+
}
|
|
23135
|
+
}
|
|
23136
|
+
} catch (err) {
|
|
23137
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23138
|
+
printText(`Failed to revalidate exchange API key: ${message}`);
|
|
23139
|
+
process.exitCode = 1;
|
|
23140
|
+
}
|
|
23141
|
+
});
|
|
23016
23142
|
return exchange;
|
|
23017
23143
|
}
|
|
23018
23144
|
|
|
@@ -24491,9 +24617,258 @@ async function getLeaderboard(baseUrl, chainId, campaignAddress, rankBy, limit =
|
|
|
24491
24617
|
return await requestJson(`${baseUrl}/campaigns/${chainId}-${campaignAddress}/leaderboard?rankBy=${rankBy}&limit=${limit}`);
|
|
24492
24618
|
}
|
|
24493
24619
|
|
|
24620
|
+
// src/services/campaign-create.ts
|
|
24621
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
24622
|
+
|
|
24623
|
+
// src/lib/blockchain.ts
|
|
24624
|
+
function sleep2(ms) {
|
|
24625
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24626
|
+
}
|
|
24627
|
+
async function waitForConfirmations(provider, txHash, opts = {}) {
|
|
24628
|
+
const minConfirmations = opts.minConfirmations ?? 1;
|
|
24629
|
+
const pollMs = opts.pollMs ?? 3000;
|
|
24630
|
+
const timeoutMs = opts.timeoutMs ?? 120000;
|
|
24631
|
+
const started = Date.now();
|
|
24632
|
+
while (Date.now() - started <= timeoutMs) {
|
|
24633
|
+
const receipt = await provider.getTransactionReceipt(txHash);
|
|
24634
|
+
if (!receipt) {
|
|
24635
|
+
await sleep2(pollMs);
|
|
24636
|
+
continue;
|
|
24637
|
+
}
|
|
24638
|
+
let confirmations = 0;
|
|
24639
|
+
if (typeof receipt.confirmations === "function") {
|
|
24640
|
+
confirmations = await receipt.confirmations();
|
|
24641
|
+
} else {
|
|
24642
|
+
confirmations = receipt.confirmations ?? 0;
|
|
24643
|
+
}
|
|
24644
|
+
opts.onProgress?.(confirmations);
|
|
24645
|
+
if (confirmations >= minConfirmations) {
|
|
24646
|
+
return receipt;
|
|
24647
|
+
}
|
|
24648
|
+
await sleep2(pollMs);
|
|
24649
|
+
}
|
|
24650
|
+
throw new Error(`Timed out waiting for transaction confirmation: ${txHash}`);
|
|
24651
|
+
}
|
|
24652
|
+
function estimateGasWithBuffer(estimated, bps = 1200) {
|
|
24653
|
+
return estimated * BigInt(1e4 + bps) / BigInt(1e4);
|
|
24654
|
+
}
|
|
24655
|
+
|
|
24656
|
+
// src/lib/contracts.ts
|
|
24657
|
+
var HUMAN_PROTOCOL_CONTRACTS = {
|
|
24658
|
+
137: {
|
|
24659
|
+
hmt: "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571BF",
|
|
24660
|
+
staking: "0x01D115E9E8bF0C58318793624CC662a030D07F1D",
|
|
24661
|
+
escrowFactory: "0x8D50dA7abe354a628a63ADfE23C19a2944612b83"
|
|
24662
|
+
},
|
|
24663
|
+
1: {
|
|
24664
|
+
hmt: "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867",
|
|
24665
|
+
staking: "0x86Af9f6Cd34B69Db1B202223C6d6D109f2491569",
|
|
24666
|
+
escrowFactory: "0xE24e5C08E28331D24758b69A5E9f383D2bDD1c98"
|
|
24667
|
+
}
|
|
24668
|
+
};
|
|
24669
|
+
var RPC_URLS = {
|
|
24670
|
+
137: [
|
|
24671
|
+
"https://polygon.drpc.org",
|
|
24672
|
+
"https://rpc.ankr.com/polygon",
|
|
24673
|
+
"https://polygon-mainnet.public.blastapi.io"
|
|
24674
|
+
],
|
|
24675
|
+
1: [
|
|
24676
|
+
"https://eth.drpc.org",
|
|
24677
|
+
"https://rpc.ankr.com/eth",
|
|
24678
|
+
"https://ethereum-rpc.publicnode.com"
|
|
24679
|
+
]
|
|
24680
|
+
};
|
|
24681
|
+
var ORACLES = {
|
|
24682
|
+
exchangeOracle: "0x5b74d007ea08217bcde942a2132df43d568a6dca",
|
|
24683
|
+
recordingOracle: "0x3a2292c684e289fe5f07737b598fe0027ead5a0e",
|
|
24684
|
+
reputationOracle: "0x1519964f5cd2d9ef162b2b1b66f33669cca065c8"
|
|
24685
|
+
};
|
|
24686
|
+
var FUND_TOKENS = {
|
|
24687
|
+
137: {
|
|
24688
|
+
USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
|
|
24689
|
+
USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
|
|
24690
|
+
},
|
|
24691
|
+
1: {
|
|
24692
|
+
USDT: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
24693
|
+
USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
24694
|
+
}
|
|
24695
|
+
};
|
|
24696
|
+
var ESCROW_FACTORY_ABI = [
|
|
24697
|
+
"function createFundAndSetupEscrow(address token, uint256 amount, string jobRequesterId, address reputationOracle, address recordingOracle, address exchangeOracle, string manifest, string manifestHash) returns (address)",
|
|
24698
|
+
"event LaunchedV2(address indexed escrow, address indexed launcher, string jobRequesterId)"
|
|
24699
|
+
];
|
|
24700
|
+
var ERC20_ABI = [
|
|
24701
|
+
"function balanceOf(address) external view returns (uint256)",
|
|
24702
|
+
"function allowance(address owner, address spender) external view returns (uint256)",
|
|
24703
|
+
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
24704
|
+
"function decimals() external view returns (uint8)"
|
|
24705
|
+
];
|
|
24706
|
+
function getContracts(chainId) {
|
|
24707
|
+
const c = HUMAN_PROTOCOL_CONTRACTS[chainId];
|
|
24708
|
+
if (!c) {
|
|
24709
|
+
throw new Error(`Unsupported chain ID: ${chainId}. Supported: ${Object.keys(HUMAN_PROTOCOL_CONTRACTS).join(", ")}`);
|
|
24710
|
+
}
|
|
24711
|
+
return c;
|
|
24712
|
+
}
|
|
24713
|
+
function getRpc(chainId) {
|
|
24714
|
+
const rpcs = RPC_URLS[chainId];
|
|
24715
|
+
if (!rpcs) {
|
|
24716
|
+
throw new Error(`No RPC URLs for chain ${chainId}`);
|
|
24717
|
+
}
|
|
24718
|
+
return rpcs[0];
|
|
24719
|
+
}
|
|
24720
|
+
function getFundTokenAddress(chainId, symbol) {
|
|
24721
|
+
const tokens = FUND_TOKENS[chainId];
|
|
24722
|
+
if (!tokens) {
|
|
24723
|
+
throw new Error(`No fund tokens for chain ${chainId}`);
|
|
24724
|
+
}
|
|
24725
|
+
const addr = tokens[symbol.toUpperCase()];
|
|
24726
|
+
if (!addr) {
|
|
24727
|
+
throw new Error(`Unknown fund token: ${symbol}. Supported: ${Object.keys(tokens).join(", ")}`);
|
|
24728
|
+
}
|
|
24729
|
+
return addr;
|
|
24730
|
+
}
|
|
24731
|
+
|
|
24732
|
+
// src/services/campaign-create.ts
|
|
24733
|
+
var CHAIN_NATIVE_SYMBOL = {
|
|
24734
|
+
137: "MATIC",
|
|
24735
|
+
1: "ETH"
|
|
24736
|
+
};
|
|
24737
|
+
function getProvider2(chainId) {
|
|
24738
|
+
return new JsonRpcProvider(getRpc(chainId), chainId, { staticNetwork: true, batchMaxCount: 1 });
|
|
24739
|
+
}
|
|
24740
|
+
function buildManifest(params) {
|
|
24741
|
+
const base = {
|
|
24742
|
+
exchange: params.exchange,
|
|
24743
|
+
start_date: params.startDate,
|
|
24744
|
+
end_date: params.endDate
|
|
24745
|
+
};
|
|
24746
|
+
switch (params.type) {
|
|
24747
|
+
case "MARKET_MAKING":
|
|
24748
|
+
return JSON.stringify({
|
|
24749
|
+
...base,
|
|
24750
|
+
type: params.type,
|
|
24751
|
+
pair: params.pair,
|
|
24752
|
+
daily_volume_target: params.dailyVolumeTarget
|
|
24753
|
+
});
|
|
24754
|
+
case "HOLDING":
|
|
24755
|
+
return JSON.stringify({
|
|
24756
|
+
...base,
|
|
24757
|
+
type: params.type,
|
|
24758
|
+
symbol: params.symbol,
|
|
24759
|
+
daily_balance_target: params.dailyBalanceTarget
|
|
24760
|
+
});
|
|
24761
|
+
case "THRESHOLD":
|
|
24762
|
+
return JSON.stringify({
|
|
24763
|
+
...base,
|
|
24764
|
+
type: params.type,
|
|
24765
|
+
symbol: params.symbol,
|
|
24766
|
+
minimum_balance_target: params.minimumBalanceTarget
|
|
24767
|
+
});
|
|
24768
|
+
default:
|
|
24769
|
+
throw new Error(`Unknown campaign type: ${params.type}`);
|
|
24770
|
+
}
|
|
24771
|
+
}
|
|
24772
|
+
function hashManifest(manifest) {
|
|
24773
|
+
return createHash2("sha1").update(manifest).digest("hex");
|
|
24774
|
+
}
|
|
24775
|
+
async function preflightCampaign(privateKey, chainId, params) {
|
|
24776
|
+
const contracts = getContracts(chainId);
|
|
24777
|
+
const provider = getProvider2(chainId);
|
|
24778
|
+
const wallet = new Wallet(privateKey, provider);
|
|
24779
|
+
const tokenAddress = getFundTokenAddress(chainId, params.fundToken);
|
|
24780
|
+
const fundTokenContract = new Contract(tokenAddress, ERC20_ABI, wallet);
|
|
24781
|
+
const [decimals, balance, allowance, nativeBalance, gasPrice] = await Promise.all([
|
|
24782
|
+
fundTokenContract.getFunction("decimals")(),
|
|
24783
|
+
fundTokenContract.getFunction("balanceOf")(wallet.address),
|
|
24784
|
+
fundTokenContract.getFunction("allowance")(wallet.address, contracts.escrowFactory),
|
|
24785
|
+
provider.getBalance(wallet.address),
|
|
24786
|
+
provider.getFeeData().then((d) => d.gasPrice ?? 0n)
|
|
24787
|
+
]);
|
|
24788
|
+
const fundAmountWei = parseUnits(params.fundAmount, decimals);
|
|
24789
|
+
const needsApproval = allowance < fundAmountWei;
|
|
24790
|
+
const manifest = buildManifest(params);
|
|
24791
|
+
const manifestHash = hashManifest(manifest);
|
|
24792
|
+
const factory = new Contract(contracts.escrowFactory, ESCROW_FACTORY_ABI, wallet);
|
|
24793
|
+
const [approveGasEstimate, createGasEstimate] = await Promise.all([
|
|
24794
|
+
needsApproval ? fundTokenContract.getFunction("approve").estimateGas(contracts.escrowFactory, fundAmountWei) : Promise.resolve(0n),
|
|
24795
|
+
factory.getFunction("createFundAndSetupEscrow").estimateGas(tokenAddress, fundAmountWei, "hufi-campaign-launcher", ORACLES.reputationOracle, ORACLES.recordingOracle, ORACLES.exchangeOracle, manifest, manifestHash)
|
|
24796
|
+
]);
|
|
24797
|
+
return {
|
|
24798
|
+
needsApproval,
|
|
24799
|
+
fundTokenBalance: balance,
|
|
24800
|
+
fundTokenDecimals: decimals,
|
|
24801
|
+
fundTokenSymbol: params.fundToken.toUpperCase(),
|
|
24802
|
+
approveGasEstimate,
|
|
24803
|
+
createGasEstimate,
|
|
24804
|
+
nativeBalance,
|
|
24805
|
+
gasPrice
|
|
24806
|
+
};
|
|
24807
|
+
}
|
|
24808
|
+
function estimateTotalGasCost(result, chainId) {
|
|
24809
|
+
const totalGasUnits = (result.needsApproval ? result.approveGasEstimate : 0n) + result.createGasEstimate;
|
|
24810
|
+
const bufferedGasUnits = estimateGasWithBuffer(totalGasUnits);
|
|
24811
|
+
const totalGasWei = bufferedGasUnits * result.gasPrice;
|
|
24812
|
+
const nativeSymbol = CHAIN_NATIVE_SYMBOL[chainId] ?? "ETH";
|
|
24813
|
+
return {
|
|
24814
|
+
totalGasWei,
|
|
24815
|
+
nativeSymbol,
|
|
24816
|
+
insufficientNative: result.nativeBalance < totalGasWei
|
|
24817
|
+
};
|
|
24818
|
+
}
|
|
24819
|
+
async function createCampaign(privateKey, chainId, params, onConfirmationProgress) {
|
|
24820
|
+
const contracts = getContracts(chainId);
|
|
24821
|
+
const provider = getProvider2(chainId);
|
|
24822
|
+
const wallet = new Wallet(privateKey, provider);
|
|
24823
|
+
const tokenAddress = getFundTokenAddress(chainId, params.fundToken);
|
|
24824
|
+
const hmtContract = new Contract(tokenAddress, ERC20_ABI, wallet);
|
|
24825
|
+
const decimals = await hmtContract.getFunction("decimals")();
|
|
24826
|
+
const fundAmountWei = parseUnits(params.fundAmount, decimals);
|
|
24827
|
+
const allowance = await hmtContract.getFunction("allowance")(wallet.address, contracts.escrowFactory);
|
|
24828
|
+
if (allowance < fundAmountWei) {
|
|
24829
|
+
const approveEstimate = await hmtContract.getFunction("approve").estimateGas(contracts.escrowFactory, fundAmountWei);
|
|
24830
|
+
const approveTx = await hmtContract.getFunction("approve")(contracts.escrowFactory, fundAmountWei, { gasLimit: estimateGasWithBuffer(approveEstimate) });
|
|
24831
|
+
await waitForConfirmations(provider, approveTx.hash, { minConfirmations: 1 });
|
|
24832
|
+
}
|
|
24833
|
+
const manifest = buildManifest(params);
|
|
24834
|
+
const manifestHash = hashManifest(manifest);
|
|
24835
|
+
const factory = new Contract(contracts.escrowFactory, ESCROW_FACTORY_ABI, wallet);
|
|
24836
|
+
const createEstimate = await factory.getFunction("createFundAndSetupEscrow").estimateGas(tokenAddress, fundAmountWei, "hufi-campaign-launcher", ORACLES.reputationOracle, ORACLES.recordingOracle, ORACLES.exchangeOracle, manifest, manifestHash);
|
|
24837
|
+
const tx = await factory.getFunction("createFundAndSetupEscrow")(tokenAddress, fundAmountWei, "hufi-campaign-launcher", ORACLES.reputationOracle, ORACLES.recordingOracle, ORACLES.exchangeOracle, manifest, manifestHash, { gasLimit: estimateGasWithBuffer(createEstimate) });
|
|
24838
|
+
const receipt = await waitForConfirmations(provider, tx.hash, {
|
|
24839
|
+
minConfirmations: 1,
|
|
24840
|
+
onProgress: (confirmations2) => {
|
|
24841
|
+
onConfirmationProgress?.(confirmations2);
|
|
24842
|
+
}
|
|
24843
|
+
});
|
|
24844
|
+
let confirmations = 1;
|
|
24845
|
+
if (typeof receipt.confirmations === "function") {
|
|
24846
|
+
confirmations = await receipt.confirmations();
|
|
24847
|
+
} else {
|
|
24848
|
+
confirmations = receipt.confirmations ?? 1;
|
|
24849
|
+
}
|
|
24850
|
+
const iface = factory.interface;
|
|
24851
|
+
let escrowAddress = "";
|
|
24852
|
+
for (const log of receipt.logs) {
|
|
24853
|
+
try {
|
|
24854
|
+
const parsed = iface.parseLog({ topics: log.topics, data: log.data });
|
|
24855
|
+
if (parsed?.name === "LaunchedV2") {
|
|
24856
|
+
escrowAddress = parsed.args.escrow;
|
|
24857
|
+
break;
|
|
24858
|
+
}
|
|
24859
|
+
} catch {}
|
|
24860
|
+
}
|
|
24861
|
+
return {
|
|
24862
|
+
escrowAddress: escrowAddress || "unknown",
|
|
24863
|
+
txHash: receipt.hash,
|
|
24864
|
+
status: "confirmed",
|
|
24865
|
+
confirmations
|
|
24866
|
+
};
|
|
24867
|
+
}
|
|
24868
|
+
|
|
24494
24869
|
// src/services/launcher/campaign.ts
|
|
24495
|
-
async function listLauncherCampaigns(baseUrl, chainId, limit = 20, status = "active") {
|
|
24496
|
-
const url = `${baseUrl}/campaigns?chain_id=${chainId}&status=${status}&limit=${limit}`;
|
|
24870
|
+
async function listLauncherCampaigns(baseUrl, chainId, limit = 20, status = "active", page = 1) {
|
|
24871
|
+
const url = `${baseUrl}/campaigns?chain_id=${chainId}&status=${status}&limit=${limit}&page=${page}`;
|
|
24497
24872
|
return await requestJson(url);
|
|
24498
24873
|
}
|
|
24499
24874
|
async function getLauncherCampaign(baseUrl, chainId, campaignAddress) {
|
|
@@ -24501,29 +24876,113 @@ async function getLauncherCampaign(baseUrl, chainId, campaignAddress) {
|
|
|
24501
24876
|
return await requestJson(url);
|
|
24502
24877
|
}
|
|
24503
24878
|
|
|
24504
|
-
// src/
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
|
|
24508
|
-
|
|
24509
|
-
|
|
24510
|
-
|
|
24879
|
+
// src/services/staking.ts
|
|
24880
|
+
var STAKING_ABI = [
|
|
24881
|
+
"function getAvailableStake(address _staker) external view returns (uint256)",
|
|
24882
|
+
"function getStakedTokens(address _staker) external view returns (uint256)",
|
|
24883
|
+
"function minimumStake() external view returns (uint256)",
|
|
24884
|
+
"function lockPeriod() external view returns (uint32)",
|
|
24885
|
+
"function stake(uint256 _tokens) external",
|
|
24886
|
+
"function unstake(uint256 _tokens) external",
|
|
24887
|
+
"function withdraw() external"
|
|
24888
|
+
];
|
|
24889
|
+
function getProvider3(chainId) {
|
|
24890
|
+
return new JsonRpcProvider(getRpc(chainId), chainId, { staticNetwork: true, batchMaxCount: 1 });
|
|
24891
|
+
}
|
|
24892
|
+
async function getStakingInfo(address, chainId) {
|
|
24893
|
+
const contracts = getContracts(chainId);
|
|
24894
|
+
const provider = getProvider3(chainId);
|
|
24895
|
+
const stakingContract = new Contract(contracts.staking, STAKING_ABI, provider);
|
|
24896
|
+
const hmtContract = new Contract(contracts.hmt, ERC20_ABI, provider);
|
|
24897
|
+
const [stakedTokens, availableStake, minimumStake, lockPeriod, hmtBalance] = await Promise.all([
|
|
24898
|
+
stakingContract.getFunction("getStakedTokens")(address),
|
|
24899
|
+
stakingContract.getFunction("getAvailableStake")(address),
|
|
24900
|
+
stakingContract.getFunction("minimumStake")(),
|
|
24901
|
+
stakingContract.getFunction("lockPeriod")(),
|
|
24902
|
+
hmtContract.getFunction("balanceOf")(address)
|
|
24903
|
+
]);
|
|
24904
|
+
const lockedTokens = stakedTokens > availableStake ? stakedTokens - availableStake : 0n;
|
|
24511
24905
|
return {
|
|
24512
|
-
|
|
24513
|
-
|
|
24514
|
-
|
|
24906
|
+
stakedTokens: formatUnits(stakedTokens, 18),
|
|
24907
|
+
availableStake: formatUnits(availableStake, 18),
|
|
24908
|
+
lockedTokens: formatUnits(lockedTokens, 18),
|
|
24909
|
+
unlockBlock: 0,
|
|
24910
|
+
minimumStake: formatUnits(minimumStake, 18),
|
|
24911
|
+
lockPeriod: Number(lockPeriod),
|
|
24912
|
+
hmtBalance: formatUnits(hmtBalance, 18),
|
|
24913
|
+
chainId
|
|
24515
24914
|
};
|
|
24516
24915
|
}
|
|
24916
|
+
async function stakeHMT(privateKey, amount, chainId) {
|
|
24917
|
+
const contracts = getContracts(chainId);
|
|
24918
|
+
const provider = getProvider3(chainId);
|
|
24919
|
+
const wallet = new Wallet(privateKey, provider);
|
|
24920
|
+
const stakingContract = new Contract(contracts.staking, STAKING_ABI, wallet);
|
|
24921
|
+
const hmtContract = new Contract(contracts.hmt, ERC20_ABI, wallet);
|
|
24922
|
+
const amountWei = parseUnits(amount, 18);
|
|
24923
|
+
const allowance = await hmtContract.getFunction("allowance")(wallet.address, contracts.staking);
|
|
24924
|
+
if (allowance < amountWei) {
|
|
24925
|
+
const approveTx = await hmtContract.getFunction("approve")(contracts.staking, amountWei);
|
|
24926
|
+
await approveTx.wait();
|
|
24927
|
+
}
|
|
24928
|
+
const tx = await stakingContract.getFunction("stake")(amountWei);
|
|
24929
|
+
const receipt = await tx.wait();
|
|
24930
|
+
return receipt.hash;
|
|
24931
|
+
}
|
|
24932
|
+
async function unstakeHMT(privateKey, amount, chainId) {
|
|
24933
|
+
const contracts = getContracts(chainId);
|
|
24934
|
+
const provider = getProvider3(chainId);
|
|
24935
|
+
const wallet = new Wallet(privateKey, provider);
|
|
24936
|
+
const stakingContract = new Contract(contracts.staking, STAKING_ABI, wallet);
|
|
24937
|
+
const amountWei = parseUnits(amount, 18);
|
|
24938
|
+
const tx = await stakingContract.getFunction("unstake")(amountWei);
|
|
24939
|
+
const receipt = await tx.wait();
|
|
24940
|
+
return receipt.hash;
|
|
24941
|
+
}
|
|
24942
|
+
async function withdrawHMT(privateKey, chainId) {
|
|
24943
|
+
const contracts = getContracts(chainId);
|
|
24944
|
+
const provider = getProvider3(chainId);
|
|
24945
|
+
const wallet = new Wallet(privateKey, provider);
|
|
24946
|
+
const stakingContract = new Contract(contracts.staking, STAKING_ABI, wallet);
|
|
24947
|
+
const tx = await stakingContract.getFunction("withdraw")();
|
|
24948
|
+
const receipt = await tx.wait();
|
|
24949
|
+
return receipt.hash;
|
|
24950
|
+
}
|
|
24951
|
+
|
|
24952
|
+
// src/lib/watch.ts
|
|
24953
|
+
function sleep3(ms) {
|
|
24954
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24955
|
+
}
|
|
24956
|
+
async function runWatchLoop(fn, options = {}) {
|
|
24957
|
+
const intervalMs = options.intervalMs ?? 1e4;
|
|
24958
|
+
const shouldContinue = options.shouldContinue ?? (() => true);
|
|
24959
|
+
while (shouldContinue()) {
|
|
24960
|
+
await fn();
|
|
24961
|
+
if (!shouldContinue()) {
|
|
24962
|
+
break;
|
|
24963
|
+
}
|
|
24964
|
+
await sleep3(intervalMs);
|
|
24965
|
+
}
|
|
24966
|
+
}
|
|
24967
|
+
|
|
24968
|
+
// src/commands/campaign.ts
|
|
24969
|
+
function formatCampaignCreateProgress(confirmations) {
|
|
24970
|
+
if (confirmations <= 0) {
|
|
24971
|
+
return "Transaction submitted. Waiting for confirmations...";
|
|
24972
|
+
}
|
|
24973
|
+
return `Confirmations: ${confirmations}`;
|
|
24974
|
+
}
|
|
24517
24975
|
function getLauncherUrl() {
|
|
24518
24976
|
const config = loadConfig();
|
|
24519
24977
|
return (config.launcherApiUrl ?? "https://cl.hu.finance").replace(/\/+$/, "");
|
|
24520
24978
|
}
|
|
24521
24979
|
function createCampaignCommand() {
|
|
24522
24980
|
const campaign = new Command("campaign").description("Campaign management commands");
|
|
24523
|
-
campaign.command("list").description("List available campaigns").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("-s, --status <status>", "Filter by status (active, completed, cancelled, to_cancel)", "active").option("-l, --limit <n>", "Max results", Number, 20).option("--json", "Output as JSON").action(async (opts) => {
|
|
24981
|
+
campaign.command("list").description("List available campaigns").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("-s, --status <status>", "Filter by status (active, completed, cancelled, to_cancel)", "active").option("--page <n>", "Page number", Number, 1).option("--page-size <n>", "Items per page", Number, 20).option("-l, --limit <n>", "Max results", Number, 20).option("--json", "Output as JSON").action(async (opts) => {
|
|
24524
24982
|
const config = loadConfig();
|
|
24525
24983
|
try {
|
|
24526
|
-
const
|
|
24984
|
+
const pageSize = opts.pageSize ?? opts.limit;
|
|
24985
|
+
const launcherResult = await listLauncherCampaigns(getLauncherUrl(), opts.chainId, pageSize, opts.status, opts.page);
|
|
24527
24986
|
let joinedKeys = new Set;
|
|
24528
24987
|
if (config.accessToken) {
|
|
24529
24988
|
const recordingUrl = config.recordingApiUrl.replace(/\/+$/, "");
|
|
@@ -24567,6 +25026,9 @@ function createCampaignCommand() {
|
|
|
24567
25026
|
if (opts.status === "active") {
|
|
24568
25027
|
printText("Tip: use --status completed, --status cancelled, or --status to_cancel to see other campaigns.");
|
|
24569
25028
|
}
|
|
25029
|
+
if (launcherResult.has_more) {
|
|
25030
|
+
printText(`Tip: more campaigns available, try --page ${opts.page + 1} --page-size ${pageSize}.`);
|
|
25031
|
+
}
|
|
24570
25032
|
}
|
|
24571
25033
|
}
|
|
24572
25034
|
} catch (err) {
|
|
@@ -24601,7 +25063,7 @@ function createCampaignCommand() {
|
|
|
24601
25063
|
}
|
|
24602
25064
|
});
|
|
24603
25065
|
campaign.command("joined").description("List campaigns you have joined").option("-l, --limit <n>", "Max results", Number, 20).option("--json", "Output as JSON").action(async (opts) => {
|
|
24604
|
-
const { baseUrl, accessToken } =
|
|
25066
|
+
const { baseUrl, accessToken } = requireAuthAddress();
|
|
24605
25067
|
try {
|
|
24606
25068
|
const result = await listJoinedCampaigns(baseUrl, accessToken, opts.limit);
|
|
24607
25069
|
if (opts.json) {
|
|
@@ -24629,7 +25091,7 @@ function createCampaignCommand() {
|
|
|
24629
25091
|
statusCmd.help();
|
|
24630
25092
|
return;
|
|
24631
25093
|
}
|
|
24632
|
-
const { baseUrl, accessToken } =
|
|
25094
|
+
const { baseUrl, accessToken } = requireAuthAddress();
|
|
24633
25095
|
try {
|
|
24634
25096
|
const status = await checkJoinStatus(baseUrl, accessToken, opts.chainId, opts.address);
|
|
24635
25097
|
if (opts.json) {
|
|
@@ -24652,7 +25114,7 @@ function createCampaignCommand() {
|
|
|
24652
25114
|
joinCmd.help();
|
|
24653
25115
|
return;
|
|
24654
25116
|
}
|
|
24655
|
-
const { baseUrl, accessToken } =
|
|
25117
|
+
const { baseUrl, accessToken } = requireAuthAddress();
|
|
24656
25118
|
try {
|
|
24657
25119
|
const joinStatus = await checkJoinStatus(baseUrl, accessToken, opts.chainId, opts.address);
|
|
24658
25120
|
if (joinStatus.status === "already_joined") {
|
|
@@ -24677,25 +25139,53 @@ function createCampaignCommand() {
|
|
|
24677
25139
|
process.exitCode = 1;
|
|
24678
25140
|
}
|
|
24679
25141
|
});
|
|
24680
|
-
const progressCmd = campaign.command("progress").description("Check your progress in a campaign").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("-a, --address <address>", "Campaign escrow address").option("--json", "Output as JSON").action(async (opts) => {
|
|
25142
|
+
const progressCmd = campaign.command("progress").description("Check your progress in a campaign").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("-a, --address <address>", "Campaign escrow address").option("--watch", "Poll continuously").option("--interval <ms>", "Polling interval in ms", Number, 1e4).option("--json", "Output as JSON").action(async (opts) => {
|
|
24681
25143
|
if (!opts.address) {
|
|
24682
25144
|
progressCmd.help();
|
|
24683
25145
|
return;
|
|
24684
25146
|
}
|
|
24685
|
-
const { baseUrl, accessToken } =
|
|
25147
|
+
const { baseUrl, accessToken } = requireAuthAddress();
|
|
24686
25148
|
try {
|
|
24687
|
-
|
|
24688
|
-
|
|
24689
|
-
|
|
24690
|
-
|
|
24691
|
-
|
|
24692
|
-
|
|
24693
|
-
|
|
25149
|
+
let running = true;
|
|
25150
|
+
let hasRunOnce = false;
|
|
25151
|
+
let watchStoppedBySignal = false;
|
|
25152
|
+
const stop = () => {
|
|
25153
|
+
running = false;
|
|
25154
|
+
watchStoppedBySignal = true;
|
|
25155
|
+
printText("Stopped watching progress.");
|
|
25156
|
+
};
|
|
25157
|
+
if (opts.watch) {
|
|
25158
|
+
process.once("SIGINT", stop);
|
|
25159
|
+
}
|
|
25160
|
+
await runWatchLoop(async () => {
|
|
25161
|
+
hasRunOnce = true;
|
|
25162
|
+
const result = await getMyProgress(baseUrl, accessToken, opts.chainId, opts.address);
|
|
25163
|
+
if (opts.json) {
|
|
25164
|
+
printJson(result);
|
|
24694
25165
|
} else {
|
|
24695
|
-
|
|
24696
|
-
|
|
25166
|
+
const r = result;
|
|
25167
|
+
if (r.message) {
|
|
25168
|
+
printText(String(r.message));
|
|
25169
|
+
} else {
|
|
25170
|
+
for (const [key, value] of Object.entries(r)) {
|
|
25171
|
+
printText(` ${key}: ${value}`);
|
|
25172
|
+
}
|
|
24697
25173
|
}
|
|
24698
25174
|
}
|
|
25175
|
+
if (opts.watch) {
|
|
25176
|
+
printText("---");
|
|
25177
|
+
}
|
|
25178
|
+
}, {
|
|
25179
|
+
intervalMs: opts.interval,
|
|
25180
|
+
shouldContinue: () => opts.watch ? running : !hasRunOnce
|
|
25181
|
+
});
|
|
25182
|
+
if (opts.watch) {
|
|
25183
|
+
process.removeListener("SIGINT", stop);
|
|
25184
|
+
if (watchStoppedBySignal) {
|
|
25185
|
+
process.exitCode = 0;
|
|
25186
|
+
}
|
|
25187
|
+
} else {
|
|
25188
|
+
return;
|
|
24699
25189
|
}
|
|
24700
25190
|
} catch (err) {
|
|
24701
25191
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -24731,120 +25221,146 @@ function createCampaignCommand() {
|
|
|
24731
25221
|
process.exitCode = 1;
|
|
24732
25222
|
}
|
|
24733
25223
|
});
|
|
25224
|
+
const VALID_TYPES = ["market_making", "holding", "threshold"];
|
|
25225
|
+
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) => {
|
|
25226
|
+
const privateKey = loadKey();
|
|
25227
|
+
if (!privateKey) {
|
|
25228
|
+
printText("No private key found. Run: hufi auth generate");
|
|
25229
|
+
process.exit(1);
|
|
25230
|
+
}
|
|
25231
|
+
const config = loadConfig();
|
|
25232
|
+
const address = config.address;
|
|
25233
|
+
if (!address) {
|
|
25234
|
+
printText("Not authenticated. Run: hufi auth login --private-key <key>");
|
|
25235
|
+
process.exit(1);
|
|
25236
|
+
}
|
|
25237
|
+
const type = opts.type.toLowerCase();
|
|
25238
|
+
if (!VALID_TYPES.includes(type)) {
|
|
25239
|
+
printText(`Invalid type: ${opts.type}. Must be one of: ${VALID_TYPES.join(", ")}`);
|
|
25240
|
+
process.exit(1);
|
|
25241
|
+
}
|
|
25242
|
+
const typeMap = {
|
|
25243
|
+
market_making: "MARKET_MAKING",
|
|
25244
|
+
holding: "HOLDING",
|
|
25245
|
+
threshold: "THRESHOLD"
|
|
25246
|
+
};
|
|
25247
|
+
const startDate = new Date(opts.startDate);
|
|
25248
|
+
const endDate = new Date(opts.endDate);
|
|
25249
|
+
const durationHours = (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60);
|
|
25250
|
+
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
|
25251
|
+
printText("Invalid date format. Use YYYY-MM-DD.");
|
|
25252
|
+
process.exit(1);
|
|
25253
|
+
}
|
|
25254
|
+
if (durationHours < 6) {
|
|
25255
|
+
printText("Campaign duration must be at least 6 hours.");
|
|
25256
|
+
process.exit(1);
|
|
25257
|
+
}
|
|
25258
|
+
if (durationHours > 100 * 24) {
|
|
25259
|
+
printText("Campaign duration must be at most 100 days.");
|
|
25260
|
+
process.exit(1);
|
|
25261
|
+
}
|
|
25262
|
+
if (type === "market_making" && !opts.dailyVolumeTarget) {
|
|
25263
|
+
printText("--daily-volume-target is required for market_making campaigns.");
|
|
25264
|
+
process.exit(1);
|
|
25265
|
+
}
|
|
25266
|
+
if (type === "holding" && !opts.dailyBalanceTarget) {
|
|
25267
|
+
printText("--daily-balance-target is required for holding campaigns.");
|
|
25268
|
+
process.exit(1);
|
|
25269
|
+
}
|
|
25270
|
+
if (type === "threshold" && !opts.minimumBalanceTarget) {
|
|
25271
|
+
printText("--minimum-balance-target is required for threshold campaigns.");
|
|
25272
|
+
process.exit(1);
|
|
25273
|
+
}
|
|
25274
|
+
const params = {
|
|
25275
|
+
type: typeMap[type],
|
|
25276
|
+
exchange: opts.exchange,
|
|
25277
|
+
pair: type === "market_making" ? opts.symbol : undefined,
|
|
25278
|
+
symbol: type !== "market_making" ? opts.symbol : undefined,
|
|
25279
|
+
startDate: startDate.toISOString(),
|
|
25280
|
+
endDate: endDate.toISOString(),
|
|
25281
|
+
fundToken: opts.fundToken.toUpperCase(),
|
|
25282
|
+
fundAmount: opts.fundAmount,
|
|
25283
|
+
dailyVolumeTarget: opts.dailyVolumeTarget,
|
|
25284
|
+
dailyBalanceTarget: opts.dailyBalanceTarget,
|
|
25285
|
+
minimumBalanceTarget: opts.minimumBalanceTarget
|
|
25286
|
+
};
|
|
25287
|
+
let minimumStake = "0";
|
|
25288
|
+
try {
|
|
25289
|
+
const stakingInfo = await getStakingInfo(address, opts.chainId);
|
|
25290
|
+
minimumStake = stakingInfo.minimumStake;
|
|
25291
|
+
if (Number(stakingInfo.stakedTokens) < Number(minimumStake)) {
|
|
25292
|
+
printText("Insufficient staked HMT to create a campaign.");
|
|
25293
|
+
printText(` Required: ${Number(minimumStake).toLocaleString()} HMT (minimum stake)`);
|
|
25294
|
+
printText(` Your stake: ${Number(stakingInfo.stakedTokens).toLocaleString()} HMT`);
|
|
25295
|
+
printText("");
|
|
25296
|
+
printText("Stake more HMT with: hufi staking stake -a <amount>");
|
|
25297
|
+
process.exit(1);
|
|
25298
|
+
}
|
|
25299
|
+
} catch (err) {
|
|
25300
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25301
|
+
printText(`Warning: could not verify staking status: ${message}`);
|
|
25302
|
+
printText("Proceeding anyway...");
|
|
25303
|
+
}
|
|
25304
|
+
let preflight;
|
|
25305
|
+
try {
|
|
25306
|
+
preflight = await preflightCampaign(privateKey, opts.chainId, params);
|
|
25307
|
+
} catch (err) {
|
|
25308
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25309
|
+
printText(`Preflight check failed: ${message}`);
|
|
25310
|
+
process.exitCode = 1;
|
|
25311
|
+
return;
|
|
25312
|
+
}
|
|
25313
|
+
const fundAmountWei = BigInt(Math.floor(Number(opts.fundAmount) * 10 ** preflight.fundTokenDecimals));
|
|
25314
|
+
if (preflight.fundTokenBalance < fundAmountWei) {
|
|
25315
|
+
printText(`Insufficient ${preflight.fundTokenSymbol} balance.`);
|
|
25316
|
+
printText(` Required: ${opts.fundAmount} ${preflight.fundTokenSymbol}`);
|
|
25317
|
+
printText(` Balance: ${formatUnits(preflight.fundTokenBalance, preflight.fundTokenDecimals)} ${preflight.fundTokenSymbol}`);
|
|
25318
|
+
process.exit(1);
|
|
25319
|
+
}
|
|
25320
|
+
const gasCost = estimateTotalGasCost(preflight, opts.chainId);
|
|
25321
|
+
printText("Campaign creation summary:");
|
|
25322
|
+
printText(` Type: ${type} on ${opts.exchange}`);
|
|
25323
|
+
printText(` Symbol: ${opts.symbol}`);
|
|
25324
|
+
printText(` Fund: ${opts.fundAmount} ${preflight.fundTokenSymbol}`);
|
|
25325
|
+
printText(` Duration: ${opts.startDate} ~ ${opts.endDate}`);
|
|
25326
|
+
printText(` Chain: ${opts.chainId}`);
|
|
25327
|
+
printText("");
|
|
25328
|
+
printText("Estimated gas costs:");
|
|
25329
|
+
if (preflight.needsApproval) {
|
|
25330
|
+
printText(` Approval: ~${preflight.approveGasEstimate.toLocaleString()} gas`);
|
|
25331
|
+
}
|
|
25332
|
+
printText(` Creation: ~${preflight.createGasEstimate.toLocaleString()} gas`);
|
|
25333
|
+
printText(` Total: ~${formatUnits(gasCost.totalGasWei, 18)} ${gasCost.nativeSymbol}`);
|
|
25334
|
+
printText("");
|
|
25335
|
+
if (gasCost.insufficientNative) {
|
|
25336
|
+
printText(`Insufficient ${gasCost.nativeSymbol} for gas.`);
|
|
25337
|
+
printText(` Balance: ${formatUnits(preflight.nativeBalance, 18)} ${gasCost.nativeSymbol}`);
|
|
25338
|
+
printText(` Needed: ~${formatUnits(gasCost.totalGasWei, 18)} ${gasCost.nativeSymbol}`);
|
|
25339
|
+
process.exit(1);
|
|
25340
|
+
}
|
|
25341
|
+
try {
|
|
25342
|
+
printText(formatCampaignCreateProgress(0));
|
|
25343
|
+
const result = await createCampaign(privateKey, opts.chainId, params, (confirmations) => {
|
|
25344
|
+
printText(formatCampaignCreateProgress(confirmations));
|
|
25345
|
+
});
|
|
25346
|
+
if (opts.json) {
|
|
25347
|
+
printJson(result);
|
|
25348
|
+
} else {
|
|
25349
|
+
printText(`Campaign created successfully!`);
|
|
25350
|
+
printText(` Escrow: ${result.escrowAddress}`);
|
|
25351
|
+
printText(` TX: ${result.txHash}`);
|
|
25352
|
+
}
|
|
25353
|
+
} catch (err) {
|
|
25354
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25355
|
+
printText(`Failed to create campaign: ${message}`);
|
|
25356
|
+
process.exitCode = 1;
|
|
25357
|
+
}
|
|
25358
|
+
});
|
|
24734
25359
|
return campaign;
|
|
24735
25360
|
}
|
|
24736
25361
|
|
|
24737
25362
|
// src/commands/staking.ts
|
|
24738
25363
|
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
25364
|
function requireKey() {
|
|
24849
25365
|
const key = loadKey();
|
|
24850
25366
|
if (!key) {
|
|
@@ -24959,9 +25475,127 @@ Send HMT to this address on Polygon (chain 137) or Ethereum (chain 1).`);
|
|
|
24959
25475
|
return staking;
|
|
24960
25476
|
}
|
|
24961
25477
|
|
|
25478
|
+
// src/lib/export.ts
|
|
25479
|
+
function toCsvRows(rows) {
|
|
25480
|
+
if (rows.length === 0) {
|
|
25481
|
+
return "";
|
|
25482
|
+
}
|
|
25483
|
+
const first = rows[0];
|
|
25484
|
+
if (!first) {
|
|
25485
|
+
return "";
|
|
25486
|
+
}
|
|
25487
|
+
const headers = Object.keys(first);
|
|
25488
|
+
const headerLine = headers.join(",");
|
|
25489
|
+
const lines = rows.map((row) => headers.map((key) => {
|
|
25490
|
+
const value = row[key];
|
|
25491
|
+
const raw = value === null || value === undefined ? "" : String(value);
|
|
25492
|
+
if (raw.includes(",") || raw.includes(`
|
|
25493
|
+
`) || raw.includes('"')) {
|
|
25494
|
+
return `"${raw.replaceAll('"', '""')}"`;
|
|
25495
|
+
}
|
|
25496
|
+
return raw;
|
|
25497
|
+
}).join(","));
|
|
25498
|
+
return [headerLine, ...lines].join(`
|
|
25499
|
+
`);
|
|
25500
|
+
}
|
|
25501
|
+
|
|
25502
|
+
// src/commands/dashboard.ts
|
|
25503
|
+
function createDashboardCommand() {
|
|
25504
|
+
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("--export <format>", "Export format: csv|json").option("--json", "Output as JSON").action(async (opts) => {
|
|
25505
|
+
const { baseUrl, accessToken, address } = requireAuthAddress();
|
|
25506
|
+
try {
|
|
25507
|
+
const [stakingInfo, campaignsResult] = await Promise.all([
|
|
25508
|
+
getStakingInfo(address, opts.chainId).catch(() => null),
|
|
25509
|
+
listJoinedCampaigns(baseUrl, accessToken, 50).catch(() => null)
|
|
25510
|
+
]);
|
|
25511
|
+
const campaigns = campaignsResult?.results ?? [];
|
|
25512
|
+
const activeCampaigns = campaigns.filter((c) => {
|
|
25513
|
+
const r = c;
|
|
25514
|
+
return r.status === "active" || r.status === "ACTIVE";
|
|
25515
|
+
});
|
|
25516
|
+
const progressPromises = activeCampaigns.map(async (c) => {
|
|
25517
|
+
const r = c;
|
|
25518
|
+
const chainId = r.chain_id ?? opts.chainId;
|
|
25519
|
+
const campaignAddress = r.address ?? r.escrow_address ?? "";
|
|
25520
|
+
if (!campaignAddress)
|
|
25521
|
+
return { campaign: c, progress: null };
|
|
25522
|
+
try {
|
|
25523
|
+
const progress = await getMyProgress(baseUrl, accessToken, chainId, campaignAddress);
|
|
25524
|
+
return { campaign: c, progress };
|
|
25525
|
+
} catch {
|
|
25526
|
+
return { campaign: c, progress: null };
|
|
25527
|
+
}
|
|
25528
|
+
});
|
|
25529
|
+
const progressResults = await Promise.all(progressPromises);
|
|
25530
|
+
const summary = {
|
|
25531
|
+
address,
|
|
25532
|
+
chainId: opts.chainId,
|
|
25533
|
+
staking: stakingInfo,
|
|
25534
|
+
activeCampaigns: progressResults
|
|
25535
|
+
};
|
|
25536
|
+
if (opts.export) {
|
|
25537
|
+
const format = String(opts.export).toLowerCase();
|
|
25538
|
+
if (format === "json") {
|
|
25539
|
+
printJson(summary);
|
|
25540
|
+
return;
|
|
25541
|
+
}
|
|
25542
|
+
if (format === "csv") {
|
|
25543
|
+
const rows = progressResults.map(({ campaign, progress }) => {
|
|
25544
|
+
const r = campaign;
|
|
25545
|
+
const p = progress ?? {};
|
|
25546
|
+
return {
|
|
25547
|
+
exchange: String(r.exchange_name ?? ""),
|
|
25548
|
+
symbol: String(r.symbol ?? ""),
|
|
25549
|
+
type: String(r.type ?? ""),
|
|
25550
|
+
campaign_address: String(r.address ?? r.escrow_address ?? ""),
|
|
25551
|
+
my_score: String(p.my_score ?? "")
|
|
25552
|
+
};
|
|
25553
|
+
});
|
|
25554
|
+
printText(toCsvRows(rows));
|
|
25555
|
+
return;
|
|
25556
|
+
}
|
|
25557
|
+
printText("Invalid export format. Use csv or json.");
|
|
25558
|
+
process.exitCode = 1;
|
|
25559
|
+
return;
|
|
25560
|
+
}
|
|
25561
|
+
if (opts.json) {
|
|
25562
|
+
printJson(summary);
|
|
25563
|
+
return;
|
|
25564
|
+
}
|
|
25565
|
+
printText(`Wallet: ${address} Chain: ${opts.chainId}
|
|
25566
|
+
`);
|
|
25567
|
+
if (stakingInfo) {
|
|
25568
|
+
printText("Staking");
|
|
25569
|
+
printText(` Staked: ${Number(stakingInfo.stakedTokens).toLocaleString()} HMT`);
|
|
25570
|
+
printText(` Available: ${Number(stakingInfo.availableStake).toLocaleString()} HMT`);
|
|
25571
|
+
printText(` Balance: ${Number(stakingInfo.hmtBalance).toLocaleString()} HMT`);
|
|
25572
|
+
printText("");
|
|
25573
|
+
}
|
|
25574
|
+
if (activeCampaigns.length === 0) {
|
|
25575
|
+
printText("No active campaigns.");
|
|
25576
|
+
} else {
|
|
25577
|
+
printText(`Active Campaigns (${activeCampaigns.length})`);
|
|
25578
|
+
for (const { campaign, progress } of progressResults) {
|
|
25579
|
+
const r = campaign;
|
|
25580
|
+
const exchange = r.exchange_name ?? "?";
|
|
25581
|
+
const symbol = r.symbol ?? "?";
|
|
25582
|
+
const type = r.type ?? "?";
|
|
25583
|
+
const score = progress && typeof progress === "object" && "my_score" in progress ? ` score: ${progress.my_score}` : "";
|
|
25584
|
+
printText(` ${exchange} ${symbol} (${type})${score}`);
|
|
25585
|
+
}
|
|
25586
|
+
}
|
|
25587
|
+
} catch (err) {
|
|
25588
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25589
|
+
printText(`Failed to load dashboard: ${message}`);
|
|
25590
|
+
process.exitCode = 1;
|
|
25591
|
+
}
|
|
25592
|
+
});
|
|
25593
|
+
return dashboard;
|
|
25594
|
+
}
|
|
25595
|
+
|
|
24962
25596
|
// src/cli.ts
|
|
24963
25597
|
var program2 = new Command;
|
|
24964
|
-
program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("0.
|
|
25598
|
+
program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("1.0.0").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
25599
|
const opts = thisCommand.opts();
|
|
24966
25600
|
if (opts.configFile) {
|
|
24967
25601
|
setConfigFile(opts.configFile);
|
|
@@ -24969,9 +25603,19 @@ program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("0
|
|
|
24969
25603
|
if (opts.keyFile) {
|
|
24970
25604
|
setKeyFile(opts.keyFile);
|
|
24971
25605
|
}
|
|
25606
|
+
const validation = validateConfig(loadConfig());
|
|
25607
|
+
if (!validation.valid) {
|
|
25608
|
+
console.error("Invalid configuration:");
|
|
25609
|
+
for (const issue of validation.issues) {
|
|
25610
|
+
console.error(`- ${issue}`);
|
|
25611
|
+
}
|
|
25612
|
+
console.error("Fix config in ~/.hufi-cli/config.json or pass --config-file.");
|
|
25613
|
+
process.exit(1);
|
|
25614
|
+
}
|
|
24972
25615
|
});
|
|
24973
25616
|
program2.addCommand(createAuthCommand());
|
|
24974
25617
|
program2.addCommand(createExchangeCommand());
|
|
24975
25618
|
program2.addCommand(createCampaignCommand());
|
|
24976
25619
|
program2.addCommand(createStakingCommand());
|
|
25620
|
+
program2.addCommand(createDashboardCommand());
|
|
24977
25621
|
program2.parse();
|