hufi-cli 0.8.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/cli.js +419 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,9 +65,13 @@ hufi campaign get --chain-id 137 --address 0x... # campaign details
|
|
|
65
65
|
hufi campaign join --address 0x... # join (chain-id defaults to 137)
|
|
66
66
|
hufi campaign status --address 0x... # check status
|
|
67
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
|
|
68
70
|
hufi campaign leaderboard --address 0x... # leaderboard
|
|
69
71
|
```
|
|
70
72
|
|
|
73
|
+
`campaign list` and `campaign get` print exact campaign timestamps and round token balances for human-readable text output.
|
|
74
|
+
|
|
71
75
|
#### Campaign Create
|
|
72
76
|
|
|
73
77
|
Requires staked HMT, gas, and fund tokens (USDT/USDC). Creates an escrow contract on-chain.
|
|
@@ -141,6 +145,8 @@ Portfolio overview — staking, active campaigns, and progress in one view.
|
|
|
141
145
|
```bash
|
|
142
146
|
hufi dashboard # full overview
|
|
143
147
|
hufi dashboard --json # machine output
|
|
148
|
+
hufi dashboard --export csv # export active campaign rows as CSV
|
|
149
|
+
hufi dashboard --export json
|
|
144
150
|
```
|
|
145
151
|
|
|
146
152
|
## Global Options
|
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) {
|
|
@@ -22971,8 +23037,8 @@ async function revalidateExchangeApiKey(baseUrl, accessToken, exchangeName) {
|
|
|
22971
23037
|
});
|
|
22972
23038
|
}
|
|
22973
23039
|
|
|
22974
|
-
// src/
|
|
22975
|
-
function
|
|
23040
|
+
// src/lib/require-auth.ts
|
|
23041
|
+
function requireAuthToken() {
|
|
22976
23042
|
const config = loadConfig();
|
|
22977
23043
|
if (!config.accessToken) {
|
|
22978
23044
|
printText("Not authenticated. Run: hufi auth login --private-key <key>");
|
|
@@ -22983,10 +23049,24 @@ function requireAuth() {
|
|
|
22983
23049
|
accessToken: config.accessToken
|
|
22984
23050
|
};
|
|
22985
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
|
|
22986
23066
|
function createExchangeCommand() {
|
|
22987
23067
|
const exchange = new Command("exchange").description("Exchange API key management");
|
|
22988
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) => {
|
|
22989
|
-
const { baseUrl, accessToken } =
|
|
23069
|
+
const { baseUrl, accessToken } = requireAuthToken();
|
|
22990
23070
|
try {
|
|
22991
23071
|
const result = await registerExchangeApiKey(baseUrl, accessToken, opts.name, opts.apiKey, opts.secretKey, opts.bitmartMemo);
|
|
22992
23072
|
if (opts.json) {
|
|
@@ -23004,7 +23084,7 @@ function createExchangeCommand() {
|
|
|
23004
23084
|
}
|
|
23005
23085
|
});
|
|
23006
23086
|
exchange.command("list").description("List registered exchange API keys").option("--json", "Output as JSON").action(async (opts) => {
|
|
23007
|
-
const { baseUrl, accessToken } =
|
|
23087
|
+
const { baseUrl, accessToken } = requireAuthToken();
|
|
23008
23088
|
try {
|
|
23009
23089
|
const keys = await listExchangeApiKeys(baseUrl, accessToken);
|
|
23010
23090
|
if (opts.json) {
|
|
@@ -23026,7 +23106,7 @@ function createExchangeCommand() {
|
|
|
23026
23106
|
}
|
|
23027
23107
|
});
|
|
23028
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) => {
|
|
23029
|
-
const { baseUrl, accessToken } =
|
|
23109
|
+
const { baseUrl, accessToken } = requireAuthToken();
|
|
23030
23110
|
try {
|
|
23031
23111
|
await deleteExchangeApiKey(baseUrl, accessToken, opts.name);
|
|
23032
23112
|
if (opts.json) {
|
|
@@ -23041,7 +23121,7 @@ function createExchangeCommand() {
|
|
|
23041
23121
|
}
|
|
23042
23122
|
});
|
|
23043
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) => {
|
|
23044
|
-
const { baseUrl, accessToken } =
|
|
23124
|
+
const { baseUrl, accessToken } = requireAuthToken();
|
|
23045
23125
|
try {
|
|
23046
23126
|
const result = await revalidateExchangeApiKey(baseUrl, accessToken, opts.name);
|
|
23047
23127
|
if (opts.json) {
|
|
@@ -24540,6 +24620,39 @@ async function getLeaderboard(baseUrl, chainId, campaignAddress, rankBy, limit =
|
|
|
24540
24620
|
// src/services/campaign-create.ts
|
|
24541
24621
|
import { createHash as createHash2 } from "node:crypto";
|
|
24542
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
|
+
|
|
24543
24656
|
// src/lib/contracts.ts
|
|
24544
24657
|
var HUMAN_PROTOCOL_CONTRACTS = {
|
|
24545
24658
|
137: {
|
|
@@ -24617,6 +24730,10 @@ function getFundTokenAddress(chainId, symbol) {
|
|
|
24617
24730
|
}
|
|
24618
24731
|
|
|
24619
24732
|
// src/services/campaign-create.ts
|
|
24733
|
+
var CHAIN_NATIVE_SYMBOL = {
|
|
24734
|
+
137: "MATIC",
|
|
24735
|
+
1: "ETH"
|
|
24736
|
+
};
|
|
24620
24737
|
function getProvider2(chainId) {
|
|
24621
24738
|
return new JsonRpcProvider(getRpc(chainId), chainId, { staticNetwork: true, batchMaxCount: 1 });
|
|
24622
24739
|
}
|
|
@@ -24655,7 +24772,51 @@ function buildManifest(params) {
|
|
|
24655
24772
|
function hashManifest(manifest) {
|
|
24656
24773
|
return createHash2("sha1").update(manifest).digest("hex");
|
|
24657
24774
|
}
|
|
24658
|
-
async function
|
|
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) {
|
|
24659
24820
|
const contracts = getContracts(chainId);
|
|
24660
24821
|
const provider = getProvider2(chainId);
|
|
24661
24822
|
const wallet = new Wallet(privateKey, provider);
|
|
@@ -24665,14 +24826,27 @@ async function createCampaign(privateKey, chainId, params) {
|
|
|
24665
24826
|
const fundAmountWei = parseUnits(params.fundAmount, decimals);
|
|
24666
24827
|
const allowance = await hmtContract.getFunction("allowance")(wallet.address, contracts.escrowFactory);
|
|
24667
24828
|
if (allowance < fundAmountWei) {
|
|
24668
|
-
const
|
|
24669
|
-
await
|
|
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 });
|
|
24670
24832
|
}
|
|
24671
24833
|
const manifest = buildManifest(params);
|
|
24672
24834
|
const manifestHash = hashManifest(manifest);
|
|
24673
24835
|
const factory = new Contract(contracts.escrowFactory, ESCROW_FACTORY_ABI, wallet);
|
|
24674
|
-
const
|
|
24675
|
-
const
|
|
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
|
+
}
|
|
24676
24850
|
const iface = factory.interface;
|
|
24677
24851
|
let escrowAddress = "";
|
|
24678
24852
|
for (const log of receipt.logs) {
|
|
@@ -24686,13 +24860,15 @@ async function createCampaign(privateKey, chainId, params) {
|
|
|
24686
24860
|
}
|
|
24687
24861
|
return {
|
|
24688
24862
|
escrowAddress: escrowAddress || "unknown",
|
|
24689
|
-
txHash: receipt.hash
|
|
24863
|
+
txHash: receipt.hash,
|
|
24864
|
+
status: "confirmed",
|
|
24865
|
+
confirmations
|
|
24690
24866
|
};
|
|
24691
24867
|
}
|
|
24692
24868
|
|
|
24693
24869
|
// src/services/launcher/campaign.ts
|
|
24694
|
-
async function listLauncherCampaigns(baseUrl, chainId, limit = 20, status = "active") {
|
|
24695
|
-
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}`;
|
|
24696
24872
|
return await requestJson(url);
|
|
24697
24873
|
}
|
|
24698
24874
|
async function getLauncherCampaign(baseUrl, chainId, campaignAddress) {
|
|
@@ -24773,18 +24949,38 @@ async function withdrawHMT(privateKey, chainId) {
|
|
|
24773
24949
|
return receipt.hash;
|
|
24774
24950
|
}
|
|
24775
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
|
+
|
|
24776
24968
|
// src/commands/campaign.ts
|
|
24777
|
-
function
|
|
24778
|
-
|
|
24779
|
-
|
|
24780
|
-
|
|
24781
|
-
|
|
24969
|
+
function formatCampaignTimestamp(value) {
|
|
24970
|
+
if (!value)
|
|
24971
|
+
return "-";
|
|
24972
|
+
return value.replace("T", " ").replace(/\.\d+Z$/, "").replace(/Z$/, "");
|
|
24973
|
+
}
|
|
24974
|
+
function formatTokenAmount(value, decimals, displayDecimals = 2) {
|
|
24975
|
+
const amount = new bignumber_default(value).dividedBy(new bignumber_default(10).pow(decimals));
|
|
24976
|
+
const rounded = amount.decimalPlaces(displayDecimals, bignumber_default.ROUND_HALF_UP);
|
|
24977
|
+
return rounded.toFormat().replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
|
|
24978
|
+
}
|
|
24979
|
+
function formatCampaignCreateProgress(confirmations) {
|
|
24980
|
+
if (confirmations <= 0) {
|
|
24981
|
+
return "Transaction submitted. Waiting for confirmations...";
|
|
24782
24982
|
}
|
|
24783
|
-
return {
|
|
24784
|
-
baseUrl: config.recordingApiUrl.replace(/\/+$/, ""),
|
|
24785
|
-
accessToken: config.accessToken,
|
|
24786
|
-
address: config.address
|
|
24787
|
-
};
|
|
24983
|
+
return `Confirmations: ${confirmations}`;
|
|
24788
24984
|
}
|
|
24789
24985
|
function getLauncherUrl() {
|
|
24790
24986
|
const config = loadConfig();
|
|
@@ -24792,10 +24988,11 @@ function getLauncherUrl() {
|
|
|
24792
24988
|
}
|
|
24793
24989
|
function createCampaignCommand() {
|
|
24794
24990
|
const campaign = new Command("campaign").description("Campaign management commands");
|
|
24795
|
-
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) => {
|
|
24991
|
+
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) => {
|
|
24796
24992
|
const config = loadConfig();
|
|
24797
24993
|
try {
|
|
24798
|
-
const
|
|
24994
|
+
const pageSize = opts.pageSize ?? opts.limit;
|
|
24995
|
+
const launcherResult = await listLauncherCampaigns(getLauncherUrl(), opts.chainId, pageSize, opts.status, opts.page);
|
|
24799
24996
|
let joinedKeys = new Set;
|
|
24800
24997
|
if (config.accessToken) {
|
|
24801
24998
|
const recordingUrl = config.recordingApiUrl.replace(/\/+$/, "");
|
|
@@ -24821,10 +25018,6 @@ function createCampaignCommand() {
|
|
|
24821
25018
|
const joined = joinedKeys.has(key);
|
|
24822
25019
|
const tag = joined ? " [JOINED]" : "";
|
|
24823
25020
|
const decimals = c.fund_token_decimals ?? 0;
|
|
24824
|
-
const fmt = (v) => {
|
|
24825
|
-
const bn = new bignumber_default(v).dividedBy(new bignumber_default(10).pow(decimals));
|
|
24826
|
-
return bn.toFormat();
|
|
24827
|
-
};
|
|
24828
25021
|
const fundAmount = new bignumber_default(c.fund_amount);
|
|
24829
25022
|
const balanceNum = new bignumber_default(c.balance);
|
|
24830
25023
|
const pct = fundAmount.gt(0) ? balanceNum.dividedBy(fundAmount).times(100).toFixed(1) : "0.0";
|
|
@@ -24832,13 +25025,16 @@ function createCampaignCommand() {
|
|
|
24832
25025
|
printText(` chain: ${c.chain_id}`);
|
|
24833
25026
|
printText(` address: ${c.address}`);
|
|
24834
25027
|
printText(` status: ${c.status}`);
|
|
24835
|
-
printText(` duration: ${c.start_date
|
|
24836
|
-
printText(` funded: ${
|
|
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}%)`);
|
|
24837
25030
|
printText("");
|
|
24838
25031
|
}
|
|
24839
25032
|
if (opts.status === "active") {
|
|
24840
25033
|
printText("Tip: use --status completed, --status cancelled, or --status to_cancel to see other campaigns.");
|
|
24841
25034
|
}
|
|
25035
|
+
if (launcherResult.has_more) {
|
|
25036
|
+
printText(`Tip: more campaigns available, try --page ${opts.page + 1} --page-size ${pageSize}.`);
|
|
25037
|
+
}
|
|
24842
25038
|
}
|
|
24843
25039
|
}
|
|
24844
25040
|
} catch (err) {
|
|
@@ -24858,12 +25054,11 @@ function createCampaignCommand() {
|
|
|
24858
25054
|
printText(` chain: ${c.chain_id}`);
|
|
24859
25055
|
printText(` status: ${c.status}`);
|
|
24860
25056
|
const showDecimals = c.fund_token_decimals ?? 0;
|
|
24861
|
-
|
|
24862
|
-
printText(`
|
|
24863
|
-
printText(`
|
|
24864
|
-
printText(`
|
|
24865
|
-
printText(`
|
|
24866
|
-
printText(` end: ${c.end_date}`);
|
|
25057
|
+
printText(` funded: ${formatTokenAmount(c.fund_amount, showDecimals)} ${c.fund_token_symbol}`);
|
|
25058
|
+
printText(` balance: ${formatTokenAmount(c.balance, showDecimals)} ${c.fund_token_symbol}`);
|
|
25059
|
+
printText(` paid: ${formatTokenAmount(c.amount_paid, showDecimals)} ${c.fund_token_symbol}`);
|
|
25060
|
+
printText(` start: ${formatCampaignTimestamp(c.start_date)}`);
|
|
25061
|
+
printText(` end: ${formatCampaignTimestamp(c.end_date)}`);
|
|
24867
25062
|
printText(` launcher: ${c.launcher}`);
|
|
24868
25063
|
}
|
|
24869
25064
|
} catch (err) {
|
|
@@ -24873,7 +25068,7 @@ function createCampaignCommand() {
|
|
|
24873
25068
|
}
|
|
24874
25069
|
});
|
|
24875
25070
|
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) => {
|
|
24876
|
-
const { baseUrl, accessToken } =
|
|
25071
|
+
const { baseUrl, accessToken } = requireAuthAddress();
|
|
24877
25072
|
try {
|
|
24878
25073
|
const result = await listJoinedCampaigns(baseUrl, accessToken, opts.limit);
|
|
24879
25074
|
if (opts.json) {
|
|
@@ -24901,7 +25096,7 @@ function createCampaignCommand() {
|
|
|
24901
25096
|
statusCmd.help();
|
|
24902
25097
|
return;
|
|
24903
25098
|
}
|
|
24904
|
-
const { baseUrl, accessToken } =
|
|
25099
|
+
const { baseUrl, accessToken } = requireAuthAddress();
|
|
24905
25100
|
try {
|
|
24906
25101
|
const status = await checkJoinStatus(baseUrl, accessToken, opts.chainId, opts.address);
|
|
24907
25102
|
if (opts.json) {
|
|
@@ -24924,7 +25119,7 @@ function createCampaignCommand() {
|
|
|
24924
25119
|
joinCmd.help();
|
|
24925
25120
|
return;
|
|
24926
25121
|
}
|
|
24927
|
-
const { baseUrl, accessToken } =
|
|
25122
|
+
const { baseUrl, accessToken } = requireAuthAddress();
|
|
24928
25123
|
try {
|
|
24929
25124
|
const joinStatus = await checkJoinStatus(baseUrl, accessToken, opts.chainId, opts.address);
|
|
24930
25125
|
if (joinStatus.status === "already_joined") {
|
|
@@ -24949,25 +25144,53 @@ function createCampaignCommand() {
|
|
|
24949
25144
|
process.exitCode = 1;
|
|
24950
25145
|
}
|
|
24951
25146
|
});
|
|
24952
|
-
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) => {
|
|
25147
|
+
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) => {
|
|
24953
25148
|
if (!opts.address) {
|
|
24954
25149
|
progressCmd.help();
|
|
24955
25150
|
return;
|
|
24956
25151
|
}
|
|
24957
|
-
const { baseUrl, accessToken } =
|
|
25152
|
+
const { baseUrl, accessToken } = requireAuthAddress();
|
|
24958
25153
|
try {
|
|
24959
|
-
|
|
24960
|
-
|
|
24961
|
-
|
|
24962
|
-
|
|
24963
|
-
|
|
24964
|
-
|
|
24965
|
-
|
|
25154
|
+
let running = true;
|
|
25155
|
+
let hasRunOnce = false;
|
|
25156
|
+
let watchStoppedBySignal = false;
|
|
25157
|
+
const stop = () => {
|
|
25158
|
+
running = false;
|
|
25159
|
+
watchStoppedBySignal = true;
|
|
25160
|
+
printText("Stopped watching progress.");
|
|
25161
|
+
};
|
|
25162
|
+
if (opts.watch) {
|
|
25163
|
+
process.once("SIGINT", stop);
|
|
25164
|
+
}
|
|
25165
|
+
await runWatchLoop(async () => {
|
|
25166
|
+
hasRunOnce = true;
|
|
25167
|
+
const result = await getMyProgress(baseUrl, accessToken, opts.chainId, opts.address);
|
|
25168
|
+
if (opts.json) {
|
|
25169
|
+
printJson(result);
|
|
24966
25170
|
} else {
|
|
24967
|
-
|
|
24968
|
-
|
|
25171
|
+
const r = result;
|
|
25172
|
+
if (r.message) {
|
|
25173
|
+
printText(String(r.message));
|
|
25174
|
+
} else {
|
|
25175
|
+
for (const [key, value] of Object.entries(r)) {
|
|
25176
|
+
printText(` ${key}: ${value}`);
|
|
25177
|
+
}
|
|
24969
25178
|
}
|
|
24970
25179
|
}
|
|
25180
|
+
if (opts.watch) {
|
|
25181
|
+
printText("---");
|
|
25182
|
+
}
|
|
25183
|
+
}, {
|
|
25184
|
+
intervalMs: opts.interval,
|
|
25185
|
+
shouldContinue: () => opts.watch ? running : !hasRunOnce
|
|
25186
|
+
});
|
|
25187
|
+
if (opts.watch) {
|
|
25188
|
+
process.removeListener("SIGINT", stop);
|
|
25189
|
+
if (watchStoppedBySignal) {
|
|
25190
|
+
process.exitCode = 0;
|
|
25191
|
+
}
|
|
25192
|
+
} else {
|
|
25193
|
+
return;
|
|
24971
25194
|
}
|
|
24972
25195
|
} catch (err) {
|
|
24973
25196
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -25053,16 +25276,6 @@ function createCampaignCommand() {
|
|
|
25053
25276
|
printText("--minimum-balance-target is required for threshold campaigns.");
|
|
25054
25277
|
process.exit(1);
|
|
25055
25278
|
}
|
|
25056
|
-
try {
|
|
25057
|
-
const stakingInfo = await getStakingInfo(address, opts.chainId);
|
|
25058
|
-
if (Number(stakingInfo.stakedTokens) <= 0) {
|
|
25059
|
-
printText("You must stake HMT before creating a campaign.");
|
|
25060
|
-
printText("Run: hufi staking stake -a <amount>");
|
|
25061
|
-
process.exit(1);
|
|
25062
|
-
}
|
|
25063
|
-
} catch {
|
|
25064
|
-
printText("Warning: could not verify staking status.");
|
|
25065
|
-
}
|
|
25066
25279
|
const params = {
|
|
25067
25280
|
type: typeMap[type],
|
|
25068
25281
|
exchange: opts.exchange,
|
|
@@ -25076,12 +25289,65 @@ function createCampaignCommand() {
|
|
|
25076
25289
|
dailyBalanceTarget: opts.dailyBalanceTarget,
|
|
25077
25290
|
minimumBalanceTarget: opts.minimumBalanceTarget
|
|
25078
25291
|
};
|
|
25292
|
+
let minimumStake = "0";
|
|
25293
|
+
try {
|
|
25294
|
+
const stakingInfo = await getStakingInfo(address, opts.chainId);
|
|
25295
|
+
minimumStake = stakingInfo.minimumStake;
|
|
25296
|
+
if (Number(stakingInfo.stakedTokens) < Number(minimumStake)) {
|
|
25297
|
+
printText("Insufficient staked HMT to create a campaign.");
|
|
25298
|
+
printText(` Required: ${Number(minimumStake).toLocaleString()} HMT (minimum stake)`);
|
|
25299
|
+
printText(` Your stake: ${Number(stakingInfo.stakedTokens).toLocaleString()} HMT`);
|
|
25300
|
+
printText("");
|
|
25301
|
+
printText("Stake more HMT with: hufi staking stake -a <amount>");
|
|
25302
|
+
process.exit(1);
|
|
25303
|
+
}
|
|
25304
|
+
} catch (err) {
|
|
25305
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25306
|
+
printText(`Warning: could not verify staking status: ${message}`);
|
|
25307
|
+
printText("Proceeding anyway...");
|
|
25308
|
+
}
|
|
25309
|
+
let preflight;
|
|
25079
25310
|
try {
|
|
25080
|
-
|
|
25081
|
-
|
|
25082
|
-
|
|
25083
|
-
printText(
|
|
25084
|
-
|
|
25311
|
+
preflight = await preflightCampaign(privateKey, opts.chainId, params);
|
|
25312
|
+
} catch (err) {
|
|
25313
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25314
|
+
printText(`Preflight check failed: ${message}`);
|
|
25315
|
+
process.exitCode = 1;
|
|
25316
|
+
return;
|
|
25317
|
+
}
|
|
25318
|
+
const fundAmountWei = BigInt(Math.floor(Number(opts.fundAmount) * 10 ** preflight.fundTokenDecimals));
|
|
25319
|
+
if (preflight.fundTokenBalance < fundAmountWei) {
|
|
25320
|
+
printText(`Insufficient ${preflight.fundTokenSymbol} balance.`);
|
|
25321
|
+
printText(` Required: ${opts.fundAmount} ${preflight.fundTokenSymbol}`);
|
|
25322
|
+
printText(` Balance: ${formatUnits(preflight.fundTokenBalance, preflight.fundTokenDecimals)} ${preflight.fundTokenSymbol}`);
|
|
25323
|
+
process.exit(1);
|
|
25324
|
+
}
|
|
25325
|
+
const gasCost = estimateTotalGasCost(preflight, opts.chainId);
|
|
25326
|
+
printText("Campaign creation summary:");
|
|
25327
|
+
printText(` Type: ${type} on ${opts.exchange}`);
|
|
25328
|
+
printText(` Symbol: ${opts.symbol}`);
|
|
25329
|
+
printText(` Fund: ${opts.fundAmount} ${preflight.fundTokenSymbol}`);
|
|
25330
|
+
printText(` Duration: ${opts.startDate} ~ ${opts.endDate}`);
|
|
25331
|
+
printText(` Chain: ${opts.chainId}`);
|
|
25332
|
+
printText("");
|
|
25333
|
+
printText("Estimated gas costs:");
|
|
25334
|
+
if (preflight.needsApproval) {
|
|
25335
|
+
printText(` Approval: ~${preflight.approveGasEstimate.toLocaleString()} gas`);
|
|
25336
|
+
}
|
|
25337
|
+
printText(` Creation: ~${preflight.createGasEstimate.toLocaleString()} gas`);
|
|
25338
|
+
printText(` Total: ~${formatUnits(gasCost.totalGasWei, 18)} ${gasCost.nativeSymbol}`);
|
|
25339
|
+
printText("");
|
|
25340
|
+
if (gasCost.insufficientNative) {
|
|
25341
|
+
printText(`Insufficient ${gasCost.nativeSymbol} for gas.`);
|
|
25342
|
+
printText(` Balance: ${formatUnits(preflight.nativeBalance, 18)} ${gasCost.nativeSymbol}`);
|
|
25343
|
+
printText(` Needed: ~${formatUnits(gasCost.totalGasWei, 18)} ${gasCost.nativeSymbol}`);
|
|
25344
|
+
process.exit(1);
|
|
25345
|
+
}
|
|
25346
|
+
try {
|
|
25347
|
+
printText(formatCampaignCreateProgress(0));
|
|
25348
|
+
const result = await createCampaign(privateKey, opts.chainId, params, (confirmations) => {
|
|
25349
|
+
printText(formatCampaignCreateProgress(confirmations));
|
|
25350
|
+
});
|
|
25085
25351
|
if (opts.json) {
|
|
25086
25352
|
printJson(result);
|
|
25087
25353
|
} else {
|
|
@@ -25214,22 +25480,34 @@ Send HMT to this address on Polygon (chain 137) or Ethereum (chain 1).`);
|
|
|
25214
25480
|
return staking;
|
|
25215
25481
|
}
|
|
25216
25482
|
|
|
25217
|
-
// src/
|
|
25218
|
-
function
|
|
25219
|
-
|
|
25220
|
-
|
|
25221
|
-
printText("Not authenticated. Run: hufi auth login --private-key <key>");
|
|
25222
|
-
process.exit(1);
|
|
25483
|
+
// src/lib/export.ts
|
|
25484
|
+
function toCsvRows(rows) {
|
|
25485
|
+
if (rows.length === 0) {
|
|
25486
|
+
return "";
|
|
25223
25487
|
}
|
|
25224
|
-
|
|
25225
|
-
|
|
25226
|
-
|
|
25227
|
-
|
|
25228
|
-
|
|
25488
|
+
const first = rows[0];
|
|
25489
|
+
if (!first) {
|
|
25490
|
+
return "";
|
|
25491
|
+
}
|
|
25492
|
+
const headers = Object.keys(first);
|
|
25493
|
+
const headerLine = headers.join(",");
|
|
25494
|
+
const lines = rows.map((row) => headers.map((key) => {
|
|
25495
|
+
const value = row[key];
|
|
25496
|
+
const raw = value === null || value === undefined ? "" : String(value);
|
|
25497
|
+
if (raw.includes(",") || raw.includes(`
|
|
25498
|
+
`) || raw.includes('"')) {
|
|
25499
|
+
return `"${raw.replaceAll('"', '""')}"`;
|
|
25500
|
+
}
|
|
25501
|
+
return raw;
|
|
25502
|
+
}).join(","));
|
|
25503
|
+
return [headerLine, ...lines].join(`
|
|
25504
|
+
`);
|
|
25229
25505
|
}
|
|
25506
|
+
|
|
25507
|
+
// src/commands/dashboard.ts
|
|
25230
25508
|
function createDashboardCommand() {
|
|
25231
|
-
const dashboard = new Command("dashboard").description("Portfolio overview — staking, campaigns, and earnings").option("-c, --chain-id <id>", "Chain ID (default: from config)", Number, getDefaultChainId()).option("--json", "Output as JSON").action(async (opts) => {
|
|
25232
|
-
const { baseUrl, accessToken, address } =
|
|
25509
|
+
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) => {
|
|
25510
|
+
const { baseUrl, accessToken, address } = requireAuthAddress();
|
|
25233
25511
|
try {
|
|
25234
25512
|
const [stakingInfo, campaignsResult] = await Promise.all([
|
|
25235
25513
|
getStakingInfo(address, opts.chainId).catch(() => null),
|
|
@@ -25254,13 +25532,39 @@ function createDashboardCommand() {
|
|
|
25254
25532
|
}
|
|
25255
25533
|
});
|
|
25256
25534
|
const progressResults = await Promise.all(progressPromises);
|
|
25535
|
+
const summary = {
|
|
25536
|
+
address,
|
|
25537
|
+
chainId: opts.chainId,
|
|
25538
|
+
staking: stakingInfo,
|
|
25539
|
+
activeCampaigns: progressResults
|
|
25540
|
+
};
|
|
25541
|
+
if (opts.export) {
|
|
25542
|
+
const format = String(opts.export).toLowerCase();
|
|
25543
|
+
if (format === "json") {
|
|
25544
|
+
printJson(summary);
|
|
25545
|
+
return;
|
|
25546
|
+
}
|
|
25547
|
+
if (format === "csv") {
|
|
25548
|
+
const rows = progressResults.map(({ campaign, progress }) => {
|
|
25549
|
+
const r = campaign;
|
|
25550
|
+
const p = progress ?? {};
|
|
25551
|
+
return {
|
|
25552
|
+
exchange: String(r.exchange_name ?? ""),
|
|
25553
|
+
symbol: String(r.symbol ?? ""),
|
|
25554
|
+
type: String(r.type ?? ""),
|
|
25555
|
+
campaign_address: String(r.address ?? r.escrow_address ?? ""),
|
|
25556
|
+
my_score: String(p.my_score ?? "")
|
|
25557
|
+
};
|
|
25558
|
+
});
|
|
25559
|
+
printText(toCsvRows(rows));
|
|
25560
|
+
return;
|
|
25561
|
+
}
|
|
25562
|
+
printText("Invalid export format. Use csv or json.");
|
|
25563
|
+
process.exitCode = 1;
|
|
25564
|
+
return;
|
|
25565
|
+
}
|
|
25257
25566
|
if (opts.json) {
|
|
25258
|
-
printJson(
|
|
25259
|
-
address,
|
|
25260
|
-
chainId: opts.chainId,
|
|
25261
|
-
staking: stakingInfo,
|
|
25262
|
-
activeCampaigns: progressResults
|
|
25263
|
-
});
|
|
25567
|
+
printJson(summary);
|
|
25264
25568
|
return;
|
|
25265
25569
|
}
|
|
25266
25570
|
printText(`Wallet: ${address} Chain: ${opts.chainId}
|
|
@@ -25296,7 +25600,7 @@ function createDashboardCommand() {
|
|
|
25296
25600
|
|
|
25297
25601
|
// src/cli.ts
|
|
25298
25602
|
var program2 = new Command;
|
|
25299
|
-
program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("0.
|
|
25603
|
+
program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("1.0.1").option("--config-file <path>", "Custom config file path (default: ~/.hufi-cli/config.json)").option("--key-file <path>", "Custom key file path (default: ~/.hufi-cli/key.json)").hook("preAction", (thisCommand) => {
|
|
25300
25604
|
const opts = thisCommand.opts();
|
|
25301
25605
|
if (opts.configFile) {
|
|
25302
25606
|
setConfigFile(opts.configFile);
|
|
@@ -25304,6 +25608,15 @@ program2.name("hufi").description("CLI tool for hu.fi DeFi platform").version("0
|
|
|
25304
25608
|
if (opts.keyFile) {
|
|
25305
25609
|
setKeyFile(opts.keyFile);
|
|
25306
25610
|
}
|
|
25611
|
+
const validation = validateConfig(loadConfig());
|
|
25612
|
+
if (!validation.valid) {
|
|
25613
|
+
console.error("Invalid configuration:");
|
|
25614
|
+
for (const issue of validation.issues) {
|
|
25615
|
+
console.error(`- ${issue}`);
|
|
25616
|
+
}
|
|
25617
|
+
console.error("Fix config in ~/.hufi-cli/config.json or pass --config-file.");
|
|
25618
|
+
process.exit(1);
|
|
25619
|
+
}
|
|
25307
25620
|
});
|
|
25308
25621
|
program2.addCommand(createAuthCommand());
|
|
25309
25622
|
program2.addCommand(createExchangeCommand());
|