@lifi/cli 0.1.1-alpha.1 → 0.1.1-alpha.2
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 +26 -0
- package/dist/lifi.js +309 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -108,6 +108,32 @@ lifi auth test # Validate key against the API
|
|
|
108
108
|
lifi health # Check API connectivity and latency
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
### Balances & Allowances
|
|
112
|
+
|
|
113
|
+
Read on-chain balances and ERC-20 allowances directly via JSON-RPC (no signing).
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Native (gas) balance — EVM or Solana
|
|
117
|
+
lifi balance native --chain ethereum --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
|
118
|
+
lifi balance native --chain solana --address 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
|
|
119
|
+
|
|
120
|
+
# ERC-20 / SPL token balance
|
|
121
|
+
lifi balance token --chain ethereum \
|
|
122
|
+
--token 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
|
|
123
|
+
--wallet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
|
124
|
+
|
|
125
|
+
# ERC-20 allowance (EVM only)
|
|
126
|
+
lifi balance allowance --chain ethereum \
|
|
127
|
+
--token 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
|
|
128
|
+
--owner 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 \
|
|
129
|
+
--spender 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE
|
|
130
|
+
|
|
131
|
+
# Override the public RPC when needed
|
|
132
|
+
lifi balance native --chain arbitrum --address 0x... --rpc https://arb1.example/v1
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
RPC URLs are pulled from the LI.FI chain catalog by default and tried in order on transient failures.
|
|
136
|
+
|
|
111
137
|
## Output Modes
|
|
112
138
|
|
|
113
139
|
| Mode | Trigger | Behaviour |
|
package/dist/lifi.js
CHANGED
|
@@ -3,6 +3,8 @@ import { Command, Option } from "commander";
|
|
|
3
3
|
import Table from "cli-table3";
|
|
4
4
|
import axios from "axios";
|
|
5
5
|
import ora from "ora";
|
|
6
|
+
import { createPublicClient, erc20Abi, http, isAddress } from "viem";
|
|
7
|
+
import { Connection, PublicKey } from "@solana/web3.js";
|
|
6
8
|
import { input } from "@inquirer/prompts";
|
|
7
9
|
//#region src/core/constants.ts
|
|
8
10
|
const API_BASE_URL = "https://li.quest/v1";
|
|
@@ -159,6 +161,312 @@ function registerAuthCommand(program) {
|
|
|
159
161
|
});
|
|
160
162
|
}
|
|
161
163
|
//#endregion
|
|
164
|
+
//#region src/core/chain-resolver.ts
|
|
165
|
+
let chainsCache = null;
|
|
166
|
+
let inflight = null;
|
|
167
|
+
async function fetchChains() {
|
|
168
|
+
if (chainsCache) return chainsCache;
|
|
169
|
+
if (inflight) return inflight;
|
|
170
|
+
inflight = api.get("/chains?chainTypes=EVM,SVM").then(({ data }) => {
|
|
171
|
+
chainsCache = data.chains;
|
|
172
|
+
inflight = null;
|
|
173
|
+
return chainsCache;
|
|
174
|
+
});
|
|
175
|
+
try {
|
|
176
|
+
return await inflight;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
inflight = null;
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function resolveChain(chainArg) {
|
|
183
|
+
if (!chainArg) throw new CliError("Chain is required", ExitCode.InvalidArgs);
|
|
184
|
+
const chains = await fetchChains();
|
|
185
|
+
const isNumeric = /^\d+$/.test(chainArg);
|
|
186
|
+
const needle = chainArg.toLowerCase();
|
|
187
|
+
const match = isNumeric ? chains.find((c) => c.id === Number(chainArg)) : chains.find((c) => c.name.toLowerCase() === needle || c.key.toLowerCase() === needle || c.metamask?.chainName?.toLowerCase() === needle);
|
|
188
|
+
if (!match) throw new CliError(`Chain "${chainArg}" not found`, ExitCode.InvalidArgs, "Run: lifi chains to see all available chains");
|
|
189
|
+
return match;
|
|
190
|
+
}
|
|
191
|
+
function resolveRpcUrls(chain, override) {
|
|
192
|
+
if (override) return [override];
|
|
193
|
+
const urls = chain.metamask?.rpcUrls ?? [];
|
|
194
|
+
if (urls.length === 0) throw new CliError(`No RPC URL available for chain "${chain.name}" (id ${chain.id})`, ExitCode.InvalidArgs, "Supply one with --rpc <url>");
|
|
195
|
+
return urls;
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/core/rpc-client.ts
|
|
199
|
+
function getStatus(e) {
|
|
200
|
+
if (typeof e.status === "number") return e.status;
|
|
201
|
+
if (typeof e.cause?.status === "number") return e.cause.status;
|
|
202
|
+
}
|
|
203
|
+
function getMessage(e) {
|
|
204
|
+
return e.shortMessage || e.message || e.details || "RPC error";
|
|
205
|
+
}
|
|
206
|
+
function isTransientRpcError(err) {
|
|
207
|
+
if (!err || typeof err !== "object") return false;
|
|
208
|
+
const e = err;
|
|
209
|
+
const status = getStatus(e);
|
|
210
|
+
if (status === 429 || typeof status === "number" && status >= 500) return true;
|
|
211
|
+
const code = e.code ?? e.cause?.code;
|
|
212
|
+
if (code === "ECONNREFUSED" || code === "ECONNRESET" || code === "ETIMEDOUT" || code === "EAI_AGAIN") return true;
|
|
213
|
+
if (code === -32005) return true;
|
|
214
|
+
const msg = `${getMessage(e)} ${e.cause?.message ?? ""}`.toLowerCase();
|
|
215
|
+
if (msg.includes("rate limit") || msg.includes("too many requests")) return true;
|
|
216
|
+
if (msg.includes("timeout") || msg.includes("timed out")) return true;
|
|
217
|
+
if (msg.includes("fetch failed") || msg.includes("network")) return true;
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
function mapRpcError(err, rpcUrls) {
|
|
221
|
+
if (err instanceof CliError) return err;
|
|
222
|
+
if (!err || typeof err !== "object") return new CliError(String(err ?? "Unknown RPC error"), ExitCode.NetworkError);
|
|
223
|
+
const e = err;
|
|
224
|
+
const raw = getMessage(e);
|
|
225
|
+
const revertMatch = raw.match(/execution reverted(?:: (.+))?/i);
|
|
226
|
+
if (revertMatch) {
|
|
227
|
+
const reason = revertMatch[1]?.trim();
|
|
228
|
+
return new CliError(reason ? `Execution reverted: ${reason}` : "Execution reverted", ExitCode.ApiError, "The contract rejected the call. Check that the address is an ERC-20 and the chain is correct.");
|
|
229
|
+
}
|
|
230
|
+
if (getStatus(e) === 429 || /rate limit|too many requests/i.test(raw)) return new CliError("RPC rate limited", ExitCode.NetworkError, "Retry shortly or pass --rpc <url> to use a private RPC.");
|
|
231
|
+
if (/timeout|timed out/i.test(raw)) return new CliError("RPC timed out", ExitCode.NetworkError, `Tried: ${rpcUrls.join(", ")}. Pass --rpc <url> to override.`);
|
|
232
|
+
return new CliError(raw, ExitCode.NetworkError, `RPC URLs tried: ${rpcUrls.join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
function makeClient(rpcUrl) {
|
|
235
|
+
return createPublicClient({ transport: http(rpcUrl, {
|
|
236
|
+
timeout: 15e3,
|
|
237
|
+
retryCount: 0
|
|
238
|
+
}) });
|
|
239
|
+
}
|
|
240
|
+
async function withFallback(rpcUrls, op) {
|
|
241
|
+
let lastErr;
|
|
242
|
+
for (const url of rpcUrls) try {
|
|
243
|
+
return await op(makeClient(url));
|
|
244
|
+
} catch (err) {
|
|
245
|
+
lastErr = err;
|
|
246
|
+
if (!isTransientRpcError(err)) break;
|
|
247
|
+
}
|
|
248
|
+
throw mapRpcError(lastErr, rpcUrls);
|
|
249
|
+
}
|
|
250
|
+
function createEvmClient(rpcUrls) {
|
|
251
|
+
if (rpcUrls.length === 0) throw new CliError("No RPC URLs provided", ExitCode.InvalidArgs);
|
|
252
|
+
return {
|
|
253
|
+
rpcUrls,
|
|
254
|
+
getNativeBalance: (address) => withFallback(rpcUrls, (c) => c.getBalance({ address })),
|
|
255
|
+
getErc20Balance: (token, wallet) => withFallback(rpcUrls, (c) => c.readContract({
|
|
256
|
+
address: token,
|
|
257
|
+
abi: erc20Abi,
|
|
258
|
+
functionName: "balanceOf",
|
|
259
|
+
args: [wallet]
|
|
260
|
+
})),
|
|
261
|
+
getErc20Allowance: (token, owner, spender) => withFallback(rpcUrls, (c) => c.readContract({
|
|
262
|
+
address: token,
|
|
263
|
+
abi: erc20Abi,
|
|
264
|
+
functionName: "allowance",
|
|
265
|
+
args: [owner, spender]
|
|
266
|
+
})),
|
|
267
|
+
getErc20Metadata: async (token) => {
|
|
268
|
+
const [symbolRes, decimalsRes] = await Promise.allSettled([withFallback(rpcUrls, (c) => c.readContract({
|
|
269
|
+
address: token,
|
|
270
|
+
abi: erc20Abi,
|
|
271
|
+
functionName: "symbol"
|
|
272
|
+
})), withFallback(rpcUrls, (c) => c.readContract({
|
|
273
|
+
address: token,
|
|
274
|
+
abi: erc20Abi,
|
|
275
|
+
functionName: "decimals"
|
|
276
|
+
}))]);
|
|
277
|
+
return {
|
|
278
|
+
symbol: symbolRes.status === "fulfilled" ? symbolRes.value : "?",
|
|
279
|
+
decimals: decimalsRes.status === "fulfilled" ? Number(decimalsRes.value) : 0
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async function withSolanaFallback(rpcUrls, op) {
|
|
285
|
+
let lastErr;
|
|
286
|
+
for (const url of rpcUrls) try {
|
|
287
|
+
return await op(new Connection(url, "confirmed"));
|
|
288
|
+
} catch (err) {
|
|
289
|
+
lastErr = err;
|
|
290
|
+
if (!isTransientRpcError(err)) break;
|
|
291
|
+
}
|
|
292
|
+
throw mapRpcError(lastErr, rpcUrls);
|
|
293
|
+
}
|
|
294
|
+
function createSolanaClient(rpcUrls) {
|
|
295
|
+
if (rpcUrls.length === 0) throw new CliError("No RPC URLs provided", ExitCode.InvalidArgs);
|
|
296
|
+
return {
|
|
297
|
+
rpcUrls,
|
|
298
|
+
getNativeBalance: async (owner) => {
|
|
299
|
+
const lamports = await withSolanaFallback(rpcUrls, (c) => c.getBalance(owner));
|
|
300
|
+
return BigInt(lamports);
|
|
301
|
+
},
|
|
302
|
+
getSplBalance: async (owner, mint) => {
|
|
303
|
+
const result = await withSolanaFallback(rpcUrls, (c) => c.getParsedTokenAccountsByOwner(owner, { mint }));
|
|
304
|
+
let total = 0n;
|
|
305
|
+
let decimals = 0;
|
|
306
|
+
for (const { account } of result.value) {
|
|
307
|
+
const info = account.data.parsed?.info?.tokenAmount;
|
|
308
|
+
if (info?.amount) total += BigInt(info.amount);
|
|
309
|
+
if (typeof info?.decimals === "number") decimals = info.decimals;
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
amount: total,
|
|
313
|
+
decimals
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function parseSolanaAddress(label, value) {
|
|
319
|
+
try {
|
|
320
|
+
return new PublicKey(value);
|
|
321
|
+
} catch {
|
|
322
|
+
throw new CliError(`Invalid ${label} address: ${value}`, ExitCode.InvalidArgs);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/commands/balance.ts
|
|
327
|
+
function assertEvm(chain, operation) {
|
|
328
|
+
if (chain.chainType !== "EVM") throw new CliError(`${operation} is only supported on EVM chains (got ${chain.chainType} chain "${chain.name}").`, ExitCode.InvalidArgs);
|
|
329
|
+
}
|
|
330
|
+
function assertEvmAddress(label, value) {
|
|
331
|
+
if (!isAddress(value)) throw new CliError(`Invalid ${label} address: ${value}`, ExitCode.InvalidArgs);
|
|
332
|
+
return value;
|
|
333
|
+
}
|
|
334
|
+
function registerBalanceCommand(program) {
|
|
335
|
+
const balance = program.command("balance").description("Read on-chain balances and ERC-20 allowances");
|
|
336
|
+
balance.command("native").description("Read the native-token balance of an address").requiredOption("--chain <chain>", "Chain id, name, or key (e.g. 1, ethereum, eth)").requiredOption("--address <address>", "Wallet address").option("--rpc <url>", "Override the RPC URL").addHelpText("after", `
|
|
337
|
+
Examples:
|
|
338
|
+
$ lifi balance native --chain ethereum --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
|
339
|
+
$ lifi balance native --chain 137 --address 0x... --json
|
|
340
|
+
$ lifi balance native --chain arbitrum --address 0x... --rpc https://arb1.example/v1`).action(async (options, command) => {
|
|
341
|
+
const opts = command.optsWithGlobals();
|
|
342
|
+
try {
|
|
343
|
+
const chain = await withSpinner("Resolving chain...", () => resolveChain(options.chain));
|
|
344
|
+
const rpcUrls = resolveRpcUrls(chain, options.rpc);
|
|
345
|
+
let result;
|
|
346
|
+
if (chain.chainType === "EVM") {
|
|
347
|
+
const address = assertEvmAddress("wallet", options.address);
|
|
348
|
+
const client = createEvmClient(rpcUrls);
|
|
349
|
+
result = {
|
|
350
|
+
address,
|
|
351
|
+
balance: (await withSpinner(`Fetching ${chain.nativeToken.symbol} balance...`, () => client.getNativeBalance(address))).toString(),
|
|
352
|
+
tokenSymbol: chain.nativeToken.symbol,
|
|
353
|
+
chainId: chain.id,
|
|
354
|
+
decimals: chain.nativeToken.decimals
|
|
355
|
+
};
|
|
356
|
+
} else if (chain.chainType === "SVM") {
|
|
357
|
+
const owner = parseSolanaAddress("wallet", options.address);
|
|
358
|
+
const client = createSolanaClient(rpcUrls);
|
|
359
|
+
const raw = await withSpinner(`Fetching ${chain.nativeToken.symbol} balance...`, () => client.getNativeBalance(owner));
|
|
360
|
+
result = {
|
|
361
|
+
address: options.address,
|
|
362
|
+
balance: raw.toString(),
|
|
363
|
+
tokenSymbol: chain.nativeToken.symbol,
|
|
364
|
+
chainId: chain.id,
|
|
365
|
+
decimals: chain.nativeToken.decimals
|
|
366
|
+
};
|
|
367
|
+
} else throw new CliError(`Unsupported chain type: ${chain.chainType}`, ExitCode.InvalidArgs);
|
|
368
|
+
if (isJsonMode(opts)) console.log(jsonOutput(result));
|
|
369
|
+
else console.log(formatTable(["Field", "Value"], [
|
|
370
|
+
["Chain", `${chain.name} (${chain.id})`],
|
|
371
|
+
["Address", result.address],
|
|
372
|
+
["Balance", `${formatAmount(result.balance, result.decimals)} ${result.tokenSymbol}`],
|
|
373
|
+
["Raw", result.balance]
|
|
374
|
+
]));
|
|
375
|
+
} catch (error) {
|
|
376
|
+
handleError(error);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
balance.command("token").description("Read the ERC-20 balance of a wallet for a specific token").requiredOption("--chain <chain>", "Chain id, name, or key").requiredOption("--token <address>", "ERC-20 contract address").requiredOption("--wallet <address>", "Wallet address").option("--rpc <url>", "Override the RPC URL").addHelpText("after", `
|
|
380
|
+
Examples:
|
|
381
|
+
$ lifi balance token --chain ethereum \\
|
|
382
|
+
--token 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \\
|
|
383
|
+
--wallet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045`).action(async (options, command) => {
|
|
384
|
+
const opts = command.optsWithGlobals();
|
|
385
|
+
try {
|
|
386
|
+
const chain = await withSpinner("Resolving chain...", () => resolveChain(options.chain));
|
|
387
|
+
const rpcUrls = resolveRpcUrls(chain, options.rpc);
|
|
388
|
+
let result;
|
|
389
|
+
if (chain.chainType === "EVM") {
|
|
390
|
+
const token = assertEvmAddress("token", options.token);
|
|
391
|
+
const wallet = assertEvmAddress("wallet", options.wallet);
|
|
392
|
+
const client = createEvmClient(rpcUrls);
|
|
393
|
+
const [rawBalance, meta] = await withSpinner("Fetching ERC-20 balance...", () => Promise.all([client.getErc20Balance(token, wallet), client.getErc20Metadata(token)]));
|
|
394
|
+
result = {
|
|
395
|
+
walletAddress: wallet,
|
|
396
|
+
tokenAddress: token,
|
|
397
|
+
balance: rawBalance.toString(),
|
|
398
|
+
tokenSymbol: meta.symbol,
|
|
399
|
+
decimals: meta.decimals,
|
|
400
|
+
chainId: chain.id
|
|
401
|
+
};
|
|
402
|
+
} else if (chain.chainType === "SVM") {
|
|
403
|
+
const mint = parseSolanaAddress("token", options.token);
|
|
404
|
+
const owner = parseSolanaAddress("wallet", options.wallet);
|
|
405
|
+
const client = createSolanaClient(rpcUrls);
|
|
406
|
+
const splResult = await withSpinner("Fetching SPL token balance...", () => client.getSplBalance(owner, mint));
|
|
407
|
+
result = {
|
|
408
|
+
walletAddress: options.wallet,
|
|
409
|
+
tokenAddress: options.token,
|
|
410
|
+
balance: splResult.amount.toString(),
|
|
411
|
+
tokenSymbol: "?",
|
|
412
|
+
decimals: splResult.decimals,
|
|
413
|
+
chainId: chain.id
|
|
414
|
+
};
|
|
415
|
+
} else throw new CliError(`Unsupported chain type: ${chain.chainType}`, ExitCode.InvalidArgs);
|
|
416
|
+
if (isJsonMode(opts)) console.log(jsonOutput(result));
|
|
417
|
+
else console.log(formatTable(["Field", "Value"], [
|
|
418
|
+
["Chain", `${chain.name} (${chain.id})`],
|
|
419
|
+
["Wallet", result.walletAddress],
|
|
420
|
+
["Token", `${result.tokenSymbol} (${result.tokenAddress})`],
|
|
421
|
+
["Balance", `${formatAmount(result.balance, result.decimals)} ${result.tokenSymbol}`],
|
|
422
|
+
["Raw", result.balance]
|
|
423
|
+
]));
|
|
424
|
+
} catch (error) {
|
|
425
|
+
handleError(error);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
balance.command("allowance").description("Read the ERC-20 allowance an owner has granted to a spender").requiredOption("--chain <chain>", "Chain id, name, or key").requiredOption("--token <address>", "ERC-20 contract address").requiredOption("--owner <address>", "Token owner (wallet) address").requiredOption("--spender <address>", "Spender address (typically the LI.FI Diamond)").option("--rpc <url>", "Override the RPC URL").addHelpText("after", `
|
|
429
|
+
Examples:
|
|
430
|
+
$ lifi balance allowance --chain ethereum \\
|
|
431
|
+
--token 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \\
|
|
432
|
+
--owner 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 \\
|
|
433
|
+
--spender 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE
|
|
434
|
+
|
|
435
|
+
Note: Allowance is an EVM-only concept. On Solana, programs use token-account
|
|
436
|
+
authority instead of ERC-20-style approvals.`).action(async (options, command) => {
|
|
437
|
+
const opts = command.optsWithGlobals();
|
|
438
|
+
try {
|
|
439
|
+
const token = assertEvmAddress("token", options.token);
|
|
440
|
+
const owner = assertEvmAddress("owner", options.owner);
|
|
441
|
+
const spender = assertEvmAddress("spender", options.spender);
|
|
442
|
+
const chain = await withSpinner("Resolving chain...", () => resolveChain(options.chain));
|
|
443
|
+
assertEvm(chain, "Allowance");
|
|
444
|
+
const client = createEvmClient(resolveRpcUrls(chain, options.rpc));
|
|
445
|
+
const [rawAllowance, meta] = await withSpinner("Fetching allowance...", () => Promise.all([client.getErc20Allowance(token, owner, spender), client.getErc20Metadata(token)]));
|
|
446
|
+
const result = {
|
|
447
|
+
tokenAddress: token,
|
|
448
|
+
ownerAddress: owner,
|
|
449
|
+
spenderAddress: spender,
|
|
450
|
+
allowance: rawAllowance.toString(),
|
|
451
|
+
tokenSymbol: meta.symbol,
|
|
452
|
+
decimals: meta.decimals,
|
|
453
|
+
chainId: chain.id
|
|
454
|
+
};
|
|
455
|
+
if (isJsonMode(opts)) console.log(jsonOutput(result));
|
|
456
|
+
else console.log(formatTable(["Field", "Value"], [
|
|
457
|
+
["Chain", `${chain.name} (${chain.id})`],
|
|
458
|
+
["Token", `${meta.symbol} (${token})`],
|
|
459
|
+
["Owner", owner],
|
|
460
|
+
["Spender", spender],
|
|
461
|
+
["Allowance", `${formatAmount(result.allowance, result.decimals)} ${result.tokenSymbol}`],
|
|
462
|
+
["Raw", result.allowance]
|
|
463
|
+
]));
|
|
464
|
+
} catch (error) {
|
|
465
|
+
handleError(error);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
//#endregion
|
|
162
470
|
//#region src/commands/chains.ts
|
|
163
471
|
function registerChainsCommand(program) {
|
|
164
472
|
program.command("chains").description("List all supported blockchain networks").addOption(new Option("--type <type>", "Filter by chain type").choices(["EVM", "SVM"])).addHelpText("after", `
|
|
@@ -615,6 +923,7 @@ function createProgram() {
|
|
|
615
923
|
registerToolsCommand(program);
|
|
616
924
|
registerGasCommand(program);
|
|
617
925
|
registerHealthCommand(program);
|
|
926
|
+
registerBalanceCommand(program);
|
|
618
927
|
return program;
|
|
619
928
|
}
|
|
620
929
|
if (process.env["VITEST"] === void 0) createProgram().parseAsync(process.argv).catch(handleError);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifi/cli",
|
|
3
|
-
"version": "0.1.1-alpha.
|
|
3
|
+
"version": "0.1.1-alpha.2",
|
|
4
4
|
"description": "CLI for the LI.FI cross-chain bridge & DEX aggregation API",
|
|
5
5
|
"homepage": "https://github.com/lifinance/lifi-cli",
|
|
6
6
|
"bugs": {
|
|
@@ -41,7 +41,9 @@
|
|
|
41
41
|
"axios": "^1.7.0",
|
|
42
42
|
"cli-table3": "^0.6.5",
|
|
43
43
|
"commander": "^12.1.0",
|
|
44
|
-
"ora": "^8.1.0"
|
|
44
|
+
"ora": "^8.1.0",
|
|
45
|
+
"viem": "^2.21.0",
|
|
46
|
+
"@solana/web3.js": "^1.95.0"
|
|
45
47
|
},
|
|
46
48
|
"lint-staged": {
|
|
47
49
|
"src/**/*.{ts,tsx}": [
|