hufi-cli 1.0.1 → 1.0.3
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/LICENSE +13 -0
- package/README.md +49 -35
- package/dist/cli.js +197 -43
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
DO WHAT THE F*** YOU WANT TO PUBLIC LICENSE
|
|
2
|
+
Version 2, December 2004
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2024 hu-fi
|
|
5
|
+
|
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
|
7
|
+
copies of this license document, and changing it is allowed as long
|
|
8
|
+
as the name is changed.
|
|
9
|
+
|
|
10
|
+
DO WHAT THE F*** YOU WANT TO PUBLIC LICENSE
|
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
12
|
+
|
|
13
|
+
0. You just DO WHAT THE F*** YOU WANT TO.
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# hufi-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](http://www.wtfpl.net/about/)
|
|
4
|
+
|
|
5
|
+
CLI tool for the [hu.fi](https://hu.finance) platform.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -14,19 +16,19 @@ Or run without installing:
|
|
|
14
16
|
bunx hufi-cli <command>
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
All examples below assume global install. Otherwise replace `hufi` with `bunx hufi-cli`.
|
|
19
|
+
All examples below assume global install. Otherwise replace `hufi-cli` with `bunx hufi-cli`.
|
|
18
20
|
|
|
19
21
|
## Quick Start
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
24
|
# Generate a wallet
|
|
23
|
-
hufi auth generate
|
|
25
|
+
hufi-cli auth generate
|
|
24
26
|
|
|
25
27
|
# Login with saved key
|
|
26
|
-
hufi auth login
|
|
28
|
+
hufi-cli auth login
|
|
27
29
|
|
|
28
30
|
# Browse campaigns
|
|
29
|
-
hufi campaign list
|
|
31
|
+
hufi-cli campaign list
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
## Commands
|
|
@@ -40,9 +42,9 @@ hufi campaign list
|
|
|
40
42
|
| `auth status` | Show current auth status |
|
|
41
43
|
|
|
42
44
|
```bash
|
|
43
|
-
hufi auth generate --json
|
|
44
|
-
hufi auth login --private-key <key>
|
|
45
|
-
hufi auth status
|
|
45
|
+
hufi-cli auth generate --json
|
|
46
|
+
hufi-cli auth login --private-key <key>
|
|
47
|
+
hufi-cli auth status
|
|
46
48
|
```
|
|
47
49
|
|
|
48
50
|
### campaign
|
|
@@ -59,15 +61,15 @@ hufi auth status
|
|
|
59
61
|
| `campaign create` | Create a new campaign (launch escrow on-chain) |
|
|
60
62
|
|
|
61
63
|
```bash
|
|
62
|
-
hufi campaign list # list active campaigns
|
|
63
|
-
hufi campaign list --status completed --chain-id 1 # completed on Ethereum
|
|
64
|
-
hufi campaign get --chain-id 137 --address 0x... # campaign details
|
|
65
|
-
hufi campaign join --address 0x... # join (chain-id defaults to 137)
|
|
66
|
-
hufi campaign status --address 0x... # check status
|
|
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
|
|
70
|
-
hufi campaign leaderboard --address 0x... # leaderboard
|
|
64
|
+
hufi-cli campaign list # list active campaigns
|
|
65
|
+
hufi-cli campaign list --status completed --chain-id 1 # completed on Ethereum
|
|
66
|
+
hufi-cli campaign get --chain-id 137 --address 0x... # campaign details
|
|
67
|
+
hufi-cli campaign join --address 0x... # join (chain-id defaults to 137)
|
|
68
|
+
hufi-cli campaign status --address 0x... # check status
|
|
69
|
+
hufi-cli campaign progress --address 0x... # your progress
|
|
70
|
+
hufi-cli campaign progress --address 0x... --watch # live updates (polling)
|
|
71
|
+
hufi-cli campaign progress --address 0x... --watch --interval 3000
|
|
72
|
+
hufi-cli campaign leaderboard --address 0x... # leaderboard
|
|
71
73
|
```
|
|
72
74
|
|
|
73
75
|
`campaign list` and `campaign get` print exact campaign timestamps and round token balances for human-readable text output.
|
|
@@ -76,23 +78,25 @@ hufi campaign leaderboard --address 0x... # leaderboard
|
|
|
76
78
|
|
|
77
79
|
Requires staked HMT, gas, and fund tokens (USDT/USDC). Creates an escrow contract on-chain.
|
|
78
80
|
|
|
81
|
+
Before broadcasting, the CLI validates the campaign type-specific target, checks the 6-hour to 100-day duration window, verifies your minimum HMT stake when possible, inspects fund token balance, and estimates gas for approval plus creation.
|
|
82
|
+
|
|
79
83
|
```bash
|
|
80
84
|
# Market Making
|
|
81
|
-
hufi campaign create \
|
|
85
|
+
hufi-cli campaign create \
|
|
82
86
|
--type market_making --exchange mexc --symbol HMT/USDT \
|
|
83
87
|
--start-date 2026-04-01 --end-date 2026-05-01 \
|
|
84
88
|
--fund-token USDT --fund-amount 10000 \
|
|
85
89
|
--daily-volume-target 50000
|
|
86
90
|
|
|
87
91
|
# Holding
|
|
88
|
-
hufi campaign create \
|
|
92
|
+
hufi-cli campaign create \
|
|
89
93
|
--type holding --exchange mexc --symbol HMT \
|
|
90
94
|
--start-date 2026-04-01 --end-date 2026-05-01 \
|
|
91
95
|
--fund-token USDT --fund-amount 5000 \
|
|
92
96
|
--daily-balance-target 1000
|
|
93
97
|
|
|
94
98
|
# Threshold
|
|
95
|
-
hufi campaign create \
|
|
99
|
+
hufi-cli campaign create \
|
|
96
100
|
--type threshold --exchange mexc --symbol HMT \
|
|
97
101
|
--start-date 2026-04-01 --end-date 2026-05-01 \
|
|
98
102
|
--fund-token USDT --fund-amount 5000 \
|
|
@@ -111,12 +115,17 @@ Running `campaign status/join/progress/leaderboard` without `-a` shows help.
|
|
|
111
115
|
| `exchange revalidate` | Revalidate an exchange API key |
|
|
112
116
|
|
|
113
117
|
```bash
|
|
114
|
-
hufi exchange register --name mexc --api-key <key> --secret-key <secret>
|
|
115
|
-
hufi exchange
|
|
116
|
-
hufi exchange
|
|
117
|
-
hufi exchange
|
|
118
|
+
hufi-cli exchange register --name mexc --api-key <key> --secret-key <secret>
|
|
119
|
+
hufi-cli exchange register --name bitmart --api-key <key> --secret-key <secret> --bitmart-memo <memo>
|
|
120
|
+
hufi-cli exchange list
|
|
121
|
+
hufi-cli exchange revalidate mexc
|
|
122
|
+
hufi-cli exchange delete mexc
|
|
118
123
|
```
|
|
119
124
|
|
|
125
|
+
`exchange register` expects the CCXT exchange name in `--name` and accepts `--bitmart-memo` for Bitmart accounts that require an extra memo value.
|
|
126
|
+
|
|
127
|
+
You must run `hufi-cli auth login` before `exchange register`, `exchange list`, `exchange delete`, or `exchange revalidate`.
|
|
128
|
+
|
|
120
129
|
### staking
|
|
121
130
|
|
|
122
131
|
| Command | Description |
|
|
@@ -128,12 +137,12 @@ hufi exchange delete --name mexc
|
|
|
128
137
|
| `staking withdraw` | Withdraw unlocked tokens after lock period |
|
|
129
138
|
|
|
130
139
|
```bash
|
|
131
|
-
hufi staking deposit # show address QR code
|
|
132
|
-
hufi staking status # check your staking
|
|
133
|
-
hufi staking status --address 0x... # check another address
|
|
134
|
-
hufi staking stake
|
|
135
|
-
hufi staking unstake
|
|
136
|
-
hufi staking withdraw # withdraw unlocked tokens
|
|
140
|
+
hufi-cli staking deposit # show address QR code
|
|
141
|
+
hufi-cli staking status # check your staking
|
|
142
|
+
hufi-cli staking status --address 0x... # check another address
|
|
143
|
+
hufi-cli staking stake 1000 # stake 1000 HMT
|
|
144
|
+
hufi-cli staking unstake 500 # unstake 500 HMT
|
|
145
|
+
hufi-cli staking withdraw # withdraw unlocked tokens
|
|
137
146
|
```
|
|
138
147
|
|
|
139
148
|
Supports Polygon (chain 137) and Ethereum (chain 1). Staking contract: `0x01D1...07F1D` on Polygon.
|
|
@@ -143,10 +152,10 @@ Supports Polygon (chain 137) and Ethereum (chain 1). Staking contract: `0x01D1..
|
|
|
143
152
|
Portfolio overview — staking, active campaigns, and progress in one view.
|
|
144
153
|
|
|
145
154
|
```bash
|
|
146
|
-
hufi dashboard # full overview
|
|
147
|
-
hufi dashboard --json # machine output
|
|
148
|
-
hufi dashboard --export csv # export active campaign rows as CSV
|
|
149
|
-
hufi dashboard --export json
|
|
155
|
+
hufi-cli dashboard # full overview
|
|
156
|
+
hufi-cli dashboard --json # machine output
|
|
157
|
+
hufi-cli dashboard --export csv # export active campaign rows as CSV
|
|
158
|
+
hufi-cli dashboard --export json
|
|
150
159
|
```
|
|
151
160
|
|
|
152
161
|
## Global Options
|
|
@@ -158,7 +167,7 @@ hufi dashboard --export json
|
|
|
158
167
|
| `-V, --version` | Show version |
|
|
159
168
|
| `-h, --help` | Show help |
|
|
160
169
|
|
|
161
|
-
|
|
170
|
+
Most command actions support `--json` for machine-readable output. Help output remains text, and commands that intentionally show help when required arguments are missing do not emit a JSON error envelope.
|
|
162
171
|
|
|
163
172
|
## Configuration
|
|
164
173
|
|
|
@@ -181,6 +190,7 @@ bun install # install deps
|
|
|
181
190
|
bun run dev -- --help # run from source
|
|
182
191
|
bun run build # build to dist/cli.js
|
|
183
192
|
bun test # unit tests
|
|
193
|
+
./test-cli.sh # full CLI integration coverage
|
|
184
194
|
bun run test:cli # integration tests
|
|
185
195
|
bun run typecheck # type check
|
|
186
196
|
```
|
|
@@ -191,3 +201,7 @@ bun run typecheck # type check
|
|
|
191
201
|
|---------|-----|
|
|
192
202
|
| Recording Oracle | https://ro.hu.finance |
|
|
193
203
|
| Campaign Launcher | https://cl.hu.finance |
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
This project is released under the [DO WHAT THE F*** YOU WANT TO PUBLIC LICENSE v2](http://www.wtfpl.net/about/). Do whatever you want with it.
|
package/dist/cli.js
CHANGED
|
@@ -22901,6 +22901,20 @@ function validateConfig(config) {
|
|
|
22901
22901
|
if (config.address !== undefined && !isEvmAddress(config.address)) {
|
|
22902
22902
|
issues.push("address must be a valid 0x-prefixed EVM address");
|
|
22903
22903
|
}
|
|
22904
|
+
if (config.rpcUrls !== undefined) {
|
|
22905
|
+
if (typeof config.rpcUrls !== "object" || config.rpcUrls === null || Array.isArray(config.rpcUrls)) {
|
|
22906
|
+
issues.push("rpcUrls must be an object mapping chain IDs to RPC URLs");
|
|
22907
|
+
} else {
|
|
22908
|
+
for (const [chainId, url] of Object.entries(config.rpcUrls)) {
|
|
22909
|
+
if (!/^\d+$/.test(chainId)) {
|
|
22910
|
+
issues.push(`rpcUrls key '${chainId}' must be a numeric chain ID`);
|
|
22911
|
+
}
|
|
22912
|
+
if (typeof url !== "string" || !isHttpUrl(url)) {
|
|
22913
|
+
issues.push(`rpcUrls.${chainId} must be a valid http/https URL`);
|
|
22914
|
+
}
|
|
22915
|
+
}
|
|
22916
|
+
}
|
|
22917
|
+
}
|
|
22904
22918
|
return {
|
|
22905
22919
|
valid: issues.length === 0,
|
|
22906
22920
|
issues
|
|
@@ -22928,7 +22942,7 @@ function createAuthCommand() {
|
|
|
22928
22942
|
auth.command("login").description("Authenticate with Recording Oracle using a private key").option("-k, --private-key <key>", "EVM private key (uses saved key if not provided)").option("-u, --api-url <url>", "Recording Oracle API URL").option("--json", "Output as JSON").action(async (opts) => {
|
|
22929
22943
|
const privateKey = opts.privateKey ?? loadKey();
|
|
22930
22944
|
if (!privateKey) {
|
|
22931
|
-
printText("No private key provided. Run: hufi auth login -k <key> or hufi auth generate");
|
|
22945
|
+
printText("No private key provided. Run: hufi-cli auth login -k <key> or hufi-cli auth generate");
|
|
22932
22946
|
process.exit(1);
|
|
22933
22947
|
}
|
|
22934
22948
|
const config = loadConfig();
|
|
@@ -22945,7 +22959,8 @@ function createAuthCommand() {
|
|
|
22945
22959
|
printJson({ address: result.address, accessToken: result.accessToken });
|
|
22946
22960
|
} else {
|
|
22947
22961
|
printText(`Authenticated as ${result.address}`);
|
|
22948
|
-
printText(`
|
|
22962
|
+
printText(`Private key loaded from ${getKeyPath()}`);
|
|
22963
|
+
printText(`Tokens saved to ${getConfigPath()}`);
|
|
22949
22964
|
}
|
|
22950
22965
|
} catch (err) {
|
|
22951
22966
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -22994,7 +23009,7 @@ function createAuthCommand() {
|
|
|
22994
23009
|
printText(`Authenticated as ${status.address}`);
|
|
22995
23010
|
printText(`API: ${status.apiUrl}`);
|
|
22996
23011
|
} else {
|
|
22997
|
-
printText("Not authenticated. Run: hufi auth login
|
|
23012
|
+
printText("Not authenticated. Run: hufi-cli auth login -k <key>");
|
|
22998
23013
|
}
|
|
22999
23014
|
}
|
|
23000
23015
|
});
|
|
@@ -23063,9 +23078,33 @@ function requireAuthAddress() {
|
|
|
23063
23078
|
}
|
|
23064
23079
|
|
|
23065
23080
|
// src/commands/exchange.ts
|
|
23081
|
+
var authHintByAction = {
|
|
23082
|
+
register: "Run: hufi auth login --private-key <key> before registering exchange API keys.",
|
|
23083
|
+
list: "Run: hufi auth login --private-key <key> before listing exchange API keys.",
|
|
23084
|
+
delete: "Run: hufi auth login --private-key <key> before deleting exchange API keys.",
|
|
23085
|
+
revalidate: "Run: hufi auth login --private-key <key> before revalidating exchange API keys."
|
|
23086
|
+
};
|
|
23087
|
+
function isUnauthorizedError(err) {
|
|
23088
|
+
return err instanceof ApiError && err.status === 401;
|
|
23089
|
+
}
|
|
23090
|
+
function formatExchangeCommandErrorMessage(action, err) {
|
|
23091
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23092
|
+
const commandTarget = action === "list" ? "exchange API keys" : "exchange API key";
|
|
23093
|
+
if (isUnauthorizedError(err)) {
|
|
23094
|
+
return `Failed to ${action} ${commandTarget}: ${message}. ${authHintByAction[action]}`;
|
|
23095
|
+
}
|
|
23096
|
+
return `Failed to ${action} ${commandTarget}: ${message}`;
|
|
23097
|
+
}
|
|
23098
|
+
function formatRevalidateText(exchangeName, result) {
|
|
23099
|
+
const lines = [`${exchangeName}: ${result.is_valid ? "valid" : "invalid"}`];
|
|
23100
|
+
if ((result.missing_permissions?.length ?? 0) > 0) {
|
|
23101
|
+
lines.push(`Missing permissions: ${result.missing_permissions?.join(", ")}`);
|
|
23102
|
+
}
|
|
23103
|
+
return lines;
|
|
23104
|
+
}
|
|
23066
23105
|
function createExchangeCommand() {
|
|
23067
23106
|
const exchange = new Command("exchange").description("Exchange API key management");
|
|
23068
|
-
exchange.command("register").description("Register a read-only exchange API key").requiredOption("-n, --name <name>", "
|
|
23107
|
+
exchange.command("register").description("Register a read-only exchange API key").requiredOption("-n, --name <name>", "CCXT 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) => {
|
|
23069
23108
|
const { baseUrl, accessToken } = requireAuthToken();
|
|
23070
23109
|
try {
|
|
23071
23110
|
const result = await registerExchangeApiKey(baseUrl, accessToken, opts.name, opts.apiKey, opts.secretKey, opts.bitmartMemo);
|
|
@@ -23078,8 +23117,7 @@ function createExchangeCommand() {
|
|
|
23078
23117
|
}
|
|
23079
23118
|
}
|
|
23080
23119
|
} catch (err) {
|
|
23081
|
-
|
|
23082
|
-
printText(`Failed to register exchange API key: ${message}`);
|
|
23120
|
+
printText(formatExchangeCommandErrorMessage("register", err));
|
|
23083
23121
|
process.exitCode = 1;
|
|
23084
23122
|
}
|
|
23085
23123
|
});
|
|
@@ -23100,42 +23138,49 @@ function createExchangeCommand() {
|
|
|
23100
23138
|
}
|
|
23101
23139
|
}
|
|
23102
23140
|
} catch (err) {
|
|
23103
|
-
|
|
23104
|
-
printText(`Failed to list exchange API keys: ${message}`);
|
|
23141
|
+
printText(formatExchangeCommandErrorMessage("list", err));
|
|
23105
23142
|
process.exitCode = 1;
|
|
23106
23143
|
}
|
|
23107
23144
|
});
|
|
23108
|
-
exchange.command("delete").description("Delete API keys for an exchange").
|
|
23145
|
+
exchange.command("delete [name]").description("Delete API keys for an exchange").usage("[name] [options]").option("-n, --name <name>", "Exchange name (e.g. mexc, bybit)").option("--json", "Output as JSON").action(async (nameArg, opts) => {
|
|
23146
|
+
const exchangeName = nameArg ?? opts.name;
|
|
23147
|
+
if (!exchangeName) {
|
|
23148
|
+
printText("Exchange name is required. Usage: hufi exchange delete <name>");
|
|
23149
|
+
process.exitCode = 1;
|
|
23150
|
+
return;
|
|
23151
|
+
}
|
|
23109
23152
|
const { baseUrl, accessToken } = requireAuthToken();
|
|
23110
23153
|
try {
|
|
23111
|
-
await deleteExchangeApiKey(baseUrl, accessToken,
|
|
23154
|
+
await deleteExchangeApiKey(baseUrl, accessToken, exchangeName);
|
|
23112
23155
|
if (opts.json) {
|
|
23113
|
-
printJson({ deleted: true, exchange_name:
|
|
23156
|
+
printJson({ deleted: true, exchange_name: exchangeName });
|
|
23114
23157
|
} else {
|
|
23115
|
-
printText(`Deleted API keys for ${
|
|
23158
|
+
printText(`Deleted API keys for ${exchangeName}.`);
|
|
23116
23159
|
}
|
|
23117
23160
|
} catch (err) {
|
|
23118
|
-
|
|
23119
|
-
printText(`Failed to delete exchange API keys: ${message}`);
|
|
23161
|
+
printText(formatExchangeCommandErrorMessage("delete", err));
|
|
23120
23162
|
process.exitCode = 1;
|
|
23121
23163
|
}
|
|
23122
23164
|
});
|
|
23123
|
-
exchange.command("revalidate").description("Revalidate exchange API key").
|
|
23165
|
+
exchange.command("revalidate [name]").description("Revalidate exchange API key").usage("[name] [options]").option("-n, --name <name>", "Exchange name (e.g. mexc, bybit)").option("--json", "Output as JSON").action(async (nameArg, opts) => {
|
|
23166
|
+
const exchangeName = nameArg ?? opts.name;
|
|
23167
|
+
if (!exchangeName) {
|
|
23168
|
+
printText("Exchange name is required. Usage: hufi exchange revalidate <name>");
|
|
23169
|
+
process.exitCode = 1;
|
|
23170
|
+
return;
|
|
23171
|
+
}
|
|
23124
23172
|
const { baseUrl, accessToken } = requireAuthToken();
|
|
23125
23173
|
try {
|
|
23126
|
-
const result = await revalidateExchangeApiKey(baseUrl, accessToken,
|
|
23174
|
+
const result = await revalidateExchangeApiKey(baseUrl, accessToken, exchangeName);
|
|
23127
23175
|
if (opts.json) {
|
|
23128
23176
|
printJson(result);
|
|
23129
23177
|
} else {
|
|
23130
|
-
const
|
|
23131
|
-
|
|
23132
|
-
if (result.missing_permissions.length > 0) {
|
|
23133
|
-
printText(`Missing permissions: ${result.missing_permissions.join(", ")}`);
|
|
23178
|
+
for (const line of formatRevalidateText(exchangeName, result)) {
|
|
23179
|
+
printText(line);
|
|
23134
23180
|
}
|
|
23135
23181
|
}
|
|
23136
23182
|
} catch (err) {
|
|
23137
|
-
|
|
23138
|
-
printText(`Failed to revalidate exchange API key: ${message}`);
|
|
23183
|
+
printText(formatExchangeCommandErrorMessage("revalidate", err));
|
|
23139
23184
|
process.exitCode = 1;
|
|
23140
23185
|
}
|
|
23141
23186
|
});
|
|
@@ -24711,6 +24756,14 @@ function getContracts(chainId) {
|
|
|
24711
24756
|
return c;
|
|
24712
24757
|
}
|
|
24713
24758
|
function getRpc(chainId) {
|
|
24759
|
+
const override = process.env[`HUFI_RPC_${chainId}`];
|
|
24760
|
+
if (override) {
|
|
24761
|
+
return override;
|
|
24762
|
+
}
|
|
24763
|
+
const configOverride = loadConfig().rpcUrls?.[String(chainId)];
|
|
24764
|
+
if (configOverride) {
|
|
24765
|
+
return configOverride;
|
|
24766
|
+
}
|
|
24714
24767
|
const rpcs = RPC_URLS[chainId];
|
|
24715
24768
|
if (!rpcs) {
|
|
24716
24769
|
throw new Error(`No RPC URLs for chain ${chainId}`);
|
|
@@ -24971,6 +25024,56 @@ function formatCampaignTimestamp(value) {
|
|
|
24971
25024
|
return "-";
|
|
24972
25025
|
return value.replace("T", " ").replace(/\.\d+Z$/, "").replace(/Z$/, "");
|
|
24973
25026
|
}
|
|
25027
|
+
function formatUtcTimestamp(value) {
|
|
25028
|
+
if (typeof value !== "string")
|
|
25029
|
+
return "-";
|
|
25030
|
+
const date = new Date(value);
|
|
25031
|
+
if (Number.isNaN(date.getTime()))
|
|
25032
|
+
return String(value);
|
|
25033
|
+
return date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
|
|
25034
|
+
}
|
|
25035
|
+
function formatMetricValue(value) {
|
|
25036
|
+
if (value === null || value === undefined)
|
|
25037
|
+
return "-";
|
|
25038
|
+
if (typeof value === "number") {
|
|
25039
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(4).replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
|
|
25040
|
+
}
|
|
25041
|
+
if (typeof value === "string") {
|
|
25042
|
+
const trimmed = value.trim();
|
|
25043
|
+
if (trimmed && /^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
25044
|
+
const num = Number(trimmed);
|
|
25045
|
+
if (!Number.isNaN(num)) {
|
|
25046
|
+
return formatMetricValue(num);
|
|
25047
|
+
}
|
|
25048
|
+
}
|
|
25049
|
+
return value;
|
|
25050
|
+
}
|
|
25051
|
+
return JSON.stringify(value);
|
|
25052
|
+
}
|
|
25053
|
+
function printAlignedMetric(label, value, labelWidth = 14) {
|
|
25054
|
+
printText(` ${label.padEnd(labelWidth)} ${formatMetricValue(value)}`);
|
|
25055
|
+
}
|
|
25056
|
+
function printCampaignProgressCard(result) {
|
|
25057
|
+
const myMeta = result.my_meta && typeof result.my_meta === "object" ? result.my_meta : {};
|
|
25058
|
+
const totalMeta = result.total_meta && typeof result.total_meta === "object" ? result.total_meta : {};
|
|
25059
|
+
const myScore = Number(result.my_score ?? 0);
|
|
25060
|
+
const totalScore = Number(totalMeta.total_score ?? 0);
|
|
25061
|
+
const scoreShare = totalScore > 0 && Number.isFinite(myScore) ? `${(myScore / totalScore * 100).toFixed(2)}%` : "-";
|
|
25062
|
+
printText("Campaign Progress");
|
|
25063
|
+
printText("-----------------");
|
|
25064
|
+
printText("[Window]");
|
|
25065
|
+
printAlignedMetric("From", formatUtcTimestamp(result.from));
|
|
25066
|
+
printAlignedMetric("To", formatUtcTimestamp(result.to));
|
|
25067
|
+
printText("");
|
|
25068
|
+
printText("[Mine]");
|
|
25069
|
+
printAlignedMetric("Score", result.my_score);
|
|
25070
|
+
printAlignedMetric("Token balance", myMeta.token_balance);
|
|
25071
|
+
printAlignedMetric("Score share", scoreShare);
|
|
25072
|
+
printText("");
|
|
25073
|
+
printText("[Totals]");
|
|
25074
|
+
printAlignedMetric("Total score", totalMeta.total_score);
|
|
25075
|
+
printAlignedMetric("Total balance", totalMeta.total_balance);
|
|
25076
|
+
}
|
|
24974
25077
|
function formatTokenAmount(value, decimals, displayDecimals = 2) {
|
|
24975
25078
|
const amount = new bignumber_default(value).dividedBy(new bignumber_default(10).pow(decimals));
|
|
24976
25079
|
const rounded = amount.decimalPlaces(displayDecimals, bignumber_default.ROUND_HALF_UP);
|
|
@@ -24986,6 +25089,34 @@ function getLauncherUrl() {
|
|
|
24986
25089
|
const config = loadConfig();
|
|
24987
25090
|
return (config.launcherApiUrl ?? "https://cl.hu.finance").replace(/\/+$/, "");
|
|
24988
25091
|
}
|
|
25092
|
+
function printCampaignSummary(campaign, options = {}) {
|
|
25093
|
+
const indent = options.indent ?? "";
|
|
25094
|
+
const joinedTag = options.joinedTag ?? "";
|
|
25095
|
+
const showLauncher = options.showLauncher ?? true;
|
|
25096
|
+
const exchange = String(campaign.exchange_name ?? "?");
|
|
25097
|
+
const symbol = String(campaign.symbol ?? "?");
|
|
25098
|
+
const type = String(campaign.type ?? "?");
|
|
25099
|
+
const chainId = String(campaign.chain_id ?? "-");
|
|
25100
|
+
const address = String(campaign.address ?? campaign.escrow_address ?? "-");
|
|
25101
|
+
const status = String(campaign.status ?? "-");
|
|
25102
|
+
const startDate = typeof campaign.start_date === "string" ? campaign.start_date : undefined;
|
|
25103
|
+
const endDate = typeof campaign.end_date === "string" ? campaign.end_date : undefined;
|
|
25104
|
+
const launcher = typeof campaign.launcher === "string" ? campaign.launcher : undefined;
|
|
25105
|
+
const decimals = Number(campaign.fund_token_decimals ?? 0);
|
|
25106
|
+
const fundAmount = new bignumber_default(String(campaign.fund_amount ?? 0));
|
|
25107
|
+
const balanceNum = new bignumber_default(String(campaign.balance ?? 0));
|
|
25108
|
+
const pct = fundAmount.gt(0) ? balanceNum.dividedBy(fundAmount).times(100).toFixed(1) : "0.0";
|
|
25109
|
+
const fundTokenSymbol = String(campaign.fund_token_symbol ?? campaign.fund_token ?? "-");
|
|
25110
|
+
printText(`${indent}${exchange} ${symbol} (${type})${joinedTag}`);
|
|
25111
|
+
printText(`${indent} chain: ${chainId}`);
|
|
25112
|
+
printText(`${indent} address: ${address}`);
|
|
25113
|
+
printText(`${indent} status: ${status}`);
|
|
25114
|
+
printText(`${indent} duration: ${formatCampaignTimestamp(startDate)} ~ ${formatCampaignTimestamp(endDate)}`);
|
|
25115
|
+
printText(`${indent} funded: ${formatTokenAmount(String(campaign.fund_amount ?? 0), decimals)} ${fundTokenSymbol} paid: ${formatTokenAmount(String(campaign.amount_paid ?? 0), decimals)} balance: ${formatTokenAmount(String(campaign.balance ?? 0), decimals)} (${pct}%)`);
|
|
25116
|
+
if (showLauncher && launcher) {
|
|
25117
|
+
printText(`${indent} launcher: ${launcher}`);
|
|
25118
|
+
}
|
|
25119
|
+
}
|
|
24989
25120
|
function createCampaignCommand() {
|
|
24990
25121
|
const campaign = new Command("campaign").description("Campaign management commands");
|
|
24991
25122
|
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) => {
|
|
@@ -25017,16 +25148,10 @@ function createCampaignCommand() {
|
|
|
25017
25148
|
const key = `${c.chain_id}:${c.address}`;
|
|
25018
25149
|
const joined = joinedKeys.has(key);
|
|
25019
25150
|
const tag = joined ? " [JOINED]" : "";
|
|
25020
|
-
|
|
25021
|
-
|
|
25022
|
-
|
|
25023
|
-
|
|
25024
|
-
printText(` ${c.exchange_name} ${c.symbol} (${c.type})${tag}`);
|
|
25025
|
-
printText(` chain: ${c.chain_id}`);
|
|
25026
|
-
printText(` address: ${c.address}`);
|
|
25027
|
-
printText(` status: ${c.status}`);
|
|
25028
|
-
printText(` duration: ${formatCampaignTimestamp(c.start_date)} ~ ${formatCampaignTimestamp(c.end_date)}`);
|
|
25029
|
-
printText(` funded: ${formatTokenAmount(c.fund_amount, decimals)} ${c.fund_token_symbol} paid: ${formatTokenAmount(c.amount_paid, decimals)} balance: ${formatTokenAmount(c.balance, decimals)} (${pct}%)`);
|
|
25151
|
+
printCampaignSummary(c, {
|
|
25152
|
+
indent: " ",
|
|
25153
|
+
joinedTag: tag
|
|
25154
|
+
});
|
|
25030
25155
|
printText("");
|
|
25031
25156
|
}
|
|
25032
25157
|
if (opts.status === "active") {
|
|
@@ -25078,10 +25203,24 @@ function createCampaignCommand() {
|
|
|
25078
25203
|
if (campaigns.length === 0) {
|
|
25079
25204
|
printText("No joined campaigns found.");
|
|
25080
25205
|
} else {
|
|
25081
|
-
printText(`Joined campaigns (${campaigns.length})
|
|
25206
|
+
printText(`Joined campaigns (${campaigns.length}):
|
|
25207
|
+
`);
|
|
25082
25208
|
for (const c of campaigns) {
|
|
25083
|
-
const
|
|
25084
|
-
|
|
25209
|
+
const record = c;
|
|
25210
|
+
const hasListMetadata = Boolean(record.exchange_name ?? record.symbol ?? record.type ?? record.address ?? record.escrow_address);
|
|
25211
|
+
if (hasListMetadata) {
|
|
25212
|
+
printCampaignSummary(record, {
|
|
25213
|
+
indent: " ",
|
|
25214
|
+
showLauncher: typeof record.launcher === "string"
|
|
25215
|
+
});
|
|
25216
|
+
} else {
|
|
25217
|
+
const exchange = String(record.exchange_name ?? "").trim();
|
|
25218
|
+
const symbol = String(record.symbol ?? "").trim();
|
|
25219
|
+
const exchangeSymbol = [exchange, symbol].filter(Boolean).join(" ");
|
|
25220
|
+
const label = record.campaign_name ?? record.name ?? (exchangeSymbol || undefined) ?? record.address ?? record.escrow_address ?? c.id ?? "(unnamed campaign)";
|
|
25221
|
+
printText(` - ${label}`);
|
|
25222
|
+
}
|
|
25223
|
+
printText("");
|
|
25085
25224
|
}
|
|
25086
25225
|
}
|
|
25087
25226
|
}
|
|
@@ -25171,9 +25310,12 @@ function createCampaignCommand() {
|
|
|
25171
25310
|
const r = result;
|
|
25172
25311
|
if (r.message) {
|
|
25173
25312
|
printText(String(r.message));
|
|
25313
|
+
} else if ("from" in r || "to" in r || "my_score" in r || "my_meta" in r || "total_meta" in r) {
|
|
25314
|
+
printCampaignProgressCard(r);
|
|
25174
25315
|
} else {
|
|
25175
25316
|
for (const [key, value] of Object.entries(r)) {
|
|
25176
|
-
|
|
25317
|
+
const displayValue = value !== null && typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
25318
|
+
printText(` ${key}: ${displayValue}`);
|
|
25177
25319
|
}
|
|
25178
25320
|
}
|
|
25179
25321
|
}
|
|
@@ -25406,11 +25548,17 @@ function createStakingCommand() {
|
|
|
25406
25548
|
process.exitCode = 1;
|
|
25407
25549
|
}
|
|
25408
25550
|
});
|
|
25409
|
-
staking.command("stake").description("Stake HMT tokens").
|
|
25551
|
+
staking.command("stake [amount]").description("Stake HMT tokens").usage("[amount] [options]").option("-a, --amount <amount>", "Amount of HMT to stake").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("--json", "Output as JSON").action(async (amountArg, opts) => {
|
|
25552
|
+
const amount = amountArg ?? opts.amount;
|
|
25553
|
+
if (!amount) {
|
|
25554
|
+
printText("Amount is required. Usage: hufi staking stake <amount>");
|
|
25555
|
+
process.exitCode = 1;
|
|
25556
|
+
return;
|
|
25557
|
+
}
|
|
25410
25558
|
const privateKey = requireKey();
|
|
25411
25559
|
try {
|
|
25412
|
-
printText(`Staking ${
|
|
25413
|
-
const hash2 = await stakeHMT(privateKey,
|
|
25560
|
+
printText(`Staking ${amount} HMT on chain ${opts.chainId}...`);
|
|
25561
|
+
const hash2 = await stakeHMT(privateKey, amount, opts.chainId);
|
|
25414
25562
|
if (opts.json) {
|
|
25415
25563
|
printJson({ txHash: hash2 });
|
|
25416
25564
|
} else {
|
|
@@ -25423,11 +25571,17 @@ function createStakingCommand() {
|
|
|
25423
25571
|
process.exitCode = 1;
|
|
25424
25572
|
}
|
|
25425
25573
|
});
|
|
25426
|
-
staking.command("unstake").description("Initiate unstaking (tokens will be locked for the lock period)").
|
|
25574
|
+
staking.command("unstake [amount]").description("Initiate unstaking (tokens will be locked for the lock period)").usage("[amount] [options]").option("-a, --amount <amount>", "Amount of HMT to unstake").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("--json", "Output as JSON").action(async (amountArg, opts) => {
|
|
25575
|
+
const amount = amountArg ?? opts.amount;
|
|
25576
|
+
if (!amount) {
|
|
25577
|
+
printText("Amount is required. Usage: hufi staking unstake <amount>");
|
|
25578
|
+
process.exitCode = 1;
|
|
25579
|
+
return;
|
|
25580
|
+
}
|
|
25427
25581
|
const privateKey = requireKey();
|
|
25428
25582
|
try {
|
|
25429
|
-
printText(`Unstaking ${
|
|
25430
|
-
const hash2 = await unstakeHMT(privateKey,
|
|
25583
|
+
printText(`Unstaking ${amount} HMT on chain ${opts.chainId}...`);
|
|
25584
|
+
const hash2 = await unstakeHMT(privateKey, amount, opts.chainId);
|
|
25431
25585
|
if (opts.json) {
|
|
25432
25586
|
printJson({ txHash: hash2 });
|
|
25433
25587
|
} else {
|
|
@@ -25600,7 +25754,7 @@ function createDashboardCommand() {
|
|
|
25600
25754
|
|
|
25601
25755
|
// src/cli.ts
|
|
25602
25756
|
var program2 = new Command;
|
|
25603
|
-
program2.name("hufi").description("CLI tool for
|
|
25757
|
+
program2.name("hufi").description("CLI tool for Hu.fi platform").version("1.0.3").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) => {
|
|
25604
25758
|
const opts = thisCommand.opts();
|
|
25605
25759
|
if (opts.configFile) {
|
|
25606
25760
|
setConfigFile(opts.configFile);
|