@hypurrquant/defi-cli 1.0.0 → 1.0.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 +10 -18
- package/dist/index.js +171 -68
- package/dist/index.js.map +1 -1
- package/dist/main.js +231 -136
- package/dist/main.js.map +1 -1
- package/dist/mcp-server.js +408 -7
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/skills/defi-cli/SKILL.md +121 -90
- package/skills/defi-cli/package.json +11 -3
- package/skills/defi-cli/references/commands.md +106 -53
- package/skills/defi-cli/references/protocols.md +100 -67
package/dist/main.js
CHANGED
|
@@ -5612,6 +5612,7 @@ var init_dist2 = __esm({
|
|
|
5612
5612
|
}
|
|
5613
5613
|
};
|
|
5614
5614
|
CTOKEN_ABI = parseAbi17([
|
|
5615
|
+
"function underlying() external view returns (address)",
|
|
5615
5616
|
"function supplyRatePerBlock() external view returns (uint256)",
|
|
5616
5617
|
"function borrowRatePerBlock() external view returns (uint256)",
|
|
5617
5618
|
"function totalSupply() external view returns (uint256)",
|
|
@@ -5625,7 +5626,10 @@ var init_dist2 = __esm({
|
|
|
5625
5626
|
CompoundV2Adapter = class {
|
|
5626
5627
|
protocolName;
|
|
5627
5628
|
defaultVtoken;
|
|
5629
|
+
vTokenCandidates;
|
|
5628
5630
|
rpcUrl;
|
|
5631
|
+
// Lazy cache: underlying asset address (lowercased) → vToken address
|
|
5632
|
+
vTokenByAsset = null;
|
|
5629
5633
|
constructor(entry, rpcUrl) {
|
|
5630
5634
|
this.protocolName = entry.name;
|
|
5631
5635
|
this.rpcUrl = rpcUrl;
|
|
@@ -5633,6 +5637,26 @@ var init_dist2 = __esm({
|
|
|
5633
5637
|
const vtoken = contracts["vusdt"] ?? contracts["vusdc"] ?? contracts["vbnb"] ?? contracts["comptroller"];
|
|
5634
5638
|
if (!vtoken) throw DefiError.contractError("Missing vToken or comptroller address");
|
|
5635
5639
|
this.defaultVtoken = vtoken;
|
|
5640
|
+
this.vTokenCandidates = Object.entries(contracts).filter(([k]) => /^v[a-z][a-z0-9]*$/i.test(k)).map(([, v]) => v);
|
|
5641
|
+
if (this.vTokenCandidates.length === 0) this.vTokenCandidates = [vtoken];
|
|
5642
|
+
}
|
|
5643
|
+
async resolveVtoken(asset) {
|
|
5644
|
+
if (!this.rpcUrl) return null;
|
|
5645
|
+
if (!this.vTokenByAsset) {
|
|
5646
|
+
const client = createPublicClient13({ transport: http13(this.rpcUrl) });
|
|
5647
|
+
const map = /* @__PURE__ */ new Map();
|
|
5648
|
+
const lookups = await Promise.allSettled(
|
|
5649
|
+
this.vTokenCandidates.map(async (v) => {
|
|
5650
|
+
const u = await client.readContract({ address: v, abi: CTOKEN_ABI, functionName: "underlying" });
|
|
5651
|
+
return [u.toLowerCase(), v];
|
|
5652
|
+
})
|
|
5653
|
+
);
|
|
5654
|
+
for (const r of lookups) {
|
|
5655
|
+
if (r.status === "fulfilled") map.set(r.value[0], r.value[1]);
|
|
5656
|
+
}
|
|
5657
|
+
this.vTokenByAsset = map;
|
|
5658
|
+
}
|
|
5659
|
+
return this.vTokenByAsset.get(asset.toLowerCase()) ?? null;
|
|
5636
5660
|
}
|
|
5637
5661
|
name() {
|
|
5638
5662
|
return this.protocolName;
|
|
@@ -5696,15 +5720,27 @@ var init_dist2 = __esm({
|
|
|
5696
5720
|
async getRates(asset) {
|
|
5697
5721
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
5698
5722
|
const client = createPublicClient13({ transport: http13(this.rpcUrl) });
|
|
5723
|
+
const vtoken = await this.resolveVtoken(asset);
|
|
5724
|
+
if (!vtoken) {
|
|
5725
|
+
return {
|
|
5726
|
+
protocol: this.protocolName,
|
|
5727
|
+
asset,
|
|
5728
|
+
supply_apy: 0,
|
|
5729
|
+
borrow_variable_apy: 0,
|
|
5730
|
+
utilization: 0,
|
|
5731
|
+
total_supply: 0n,
|
|
5732
|
+
total_borrow: 0n
|
|
5733
|
+
};
|
|
5734
|
+
}
|
|
5699
5735
|
const [supplyRate, borrowRate, totalSupply, totalBorrows] = await Promise.all([
|
|
5700
|
-
client.readContract({ address:
|
|
5736
|
+
client.readContract({ address: vtoken, abi: CTOKEN_ABI, functionName: "supplyRatePerBlock" }).catch((e) => {
|
|
5701
5737
|
throw DefiError.rpcError(`[${this.protocolName}] supplyRatePerBlock failed: ${e}`);
|
|
5702
5738
|
}),
|
|
5703
|
-
client.readContract({ address:
|
|
5739
|
+
client.readContract({ address: vtoken, abi: CTOKEN_ABI, functionName: "borrowRatePerBlock" }).catch((e) => {
|
|
5704
5740
|
throw DefiError.rpcError(`[${this.protocolName}] borrowRatePerBlock failed: ${e}`);
|
|
5705
5741
|
}),
|
|
5706
|
-
client.readContract({ address:
|
|
5707
|
-
client.readContract({ address:
|
|
5742
|
+
client.readContract({ address: vtoken, abi: CTOKEN_ABI, functionName: "totalSupply" }).catch(() => 0n),
|
|
5743
|
+
client.readContract({ address: vtoken, abi: CTOKEN_ABI, functionName: "totalBorrows" }).catch(() => 0n)
|
|
5708
5744
|
]);
|
|
5709
5745
|
const supplyPerBlock = Number(supplyRate) / 1e18;
|
|
5710
5746
|
const borrowPerBlock = Number(borrowRate) / 1e18;
|
|
@@ -5730,6 +5766,7 @@ var init_dist2 = __esm({
|
|
|
5730
5766
|
}
|
|
5731
5767
|
};
|
|
5732
5768
|
COMET_ABI = parseAbi18([
|
|
5769
|
+
"function baseToken() external view returns (address)",
|
|
5733
5770
|
"function getUtilization() external view returns (uint256)",
|
|
5734
5771
|
"function getSupplyRate(uint256 utilization) external view returns (uint64)",
|
|
5735
5772
|
"function getBorrowRate(uint256 utilization) external view returns (uint64)",
|
|
@@ -5815,6 +5852,24 @@ var init_dist2 = __esm({
|
|
|
5815
5852
|
async getRates(asset) {
|
|
5816
5853
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
5817
5854
|
const client = createPublicClient14({ transport: http14(this.rpcUrl) });
|
|
5855
|
+
const baseToken = await client.readContract({
|
|
5856
|
+
address: this.comet,
|
|
5857
|
+
abi: COMET_ABI,
|
|
5858
|
+
functionName: "baseToken"
|
|
5859
|
+
}).catch((e) => {
|
|
5860
|
+
throw DefiError.rpcError(`[${this.protocolName}] baseToken failed: ${e}`);
|
|
5861
|
+
});
|
|
5862
|
+
if (baseToken.toLowerCase() !== asset.toLowerCase()) {
|
|
5863
|
+
return {
|
|
5864
|
+
protocol: this.protocolName,
|
|
5865
|
+
asset,
|
|
5866
|
+
supply_apy: 0,
|
|
5867
|
+
borrow_variable_apy: 0,
|
|
5868
|
+
utilization: 0,
|
|
5869
|
+
total_supply: 0n,
|
|
5870
|
+
total_borrow: 0n
|
|
5871
|
+
};
|
|
5872
|
+
}
|
|
5818
5873
|
const utilization = await client.readContract({
|
|
5819
5874
|
address: this.comet,
|
|
5820
5875
|
abi: COMET_ABI,
|
|
@@ -9418,11 +9473,16 @@ function registerLending(parent, getOpts, makeExecutor2) {
|
|
|
9418
9473
|
const rates = await adapter.getRates(asset);
|
|
9419
9474
|
printOutput(rates, getOpts());
|
|
9420
9475
|
});
|
|
9421
|
-
lending.command("position").description("Show current lending position").requiredOption("--protocol <protocol>", "Protocol slug").
|
|
9476
|
+
lending.command("position").description("Show current lending position").requiredOption("--protocol <protocol>", "Protocol slug").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
9422
9477
|
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
9423
9478
|
if (!ctx) return;
|
|
9479
|
+
const address = opts.address ?? process.env["DEFI_WALLET_ADDRESS"];
|
|
9480
|
+
if (!address) {
|
|
9481
|
+
printOutput({ error: "--address required (or set DEFI_WALLET_ADDRESS)" }, getOpts());
|
|
9482
|
+
return;
|
|
9483
|
+
}
|
|
9424
9484
|
const adapter = createLending(ctx.protocol, ctx.rpcUrl);
|
|
9425
|
-
const position = await adapter.getUserPosition(
|
|
9485
|
+
const position = await adapter.getUserPosition(address);
|
|
9426
9486
|
printOutput(position, getOpts());
|
|
9427
9487
|
});
|
|
9428
9488
|
lending.command("supply").description("Supply an asset to a lending protocol").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount to supply in wei").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
|
|
@@ -9696,39 +9756,6 @@ async function scanRatesForExecute(registry, asset) {
|
|
|
9696
9756
|
}
|
|
9697
9757
|
function registerYield(parent, getOpts, makeExecutor2) {
|
|
9698
9758
|
const yieldCmd = parent.command("yield").description("Yield operations: compare, scan, optimize, execute");
|
|
9699
|
-
yieldCmd.option("--asset <token>", "Token symbol or address", "USDC").action(async (opts) => {
|
|
9700
|
-
try {
|
|
9701
|
-
const registry = Registry.loadEmbedded();
|
|
9702
|
-
const asset = opts.asset;
|
|
9703
|
-
const specifiedChain = parent.opts().chain;
|
|
9704
|
-
const chainKeys = specifiedChain ? [specifiedChain.toLowerCase()] : Array.from(registry.chains.keys());
|
|
9705
|
-
const allRates = [];
|
|
9706
|
-
for (const chainKey of chainKeys) {
|
|
9707
|
-
try {
|
|
9708
|
-
const chain = registry.getChain(chainKey);
|
|
9709
|
-
const rpc = chain.effectiveRpcUrl();
|
|
9710
|
-
let assetAddr;
|
|
9711
|
-
try {
|
|
9712
|
-
assetAddr = resolveAsset(registry, chainKey, asset);
|
|
9713
|
-
} catch {
|
|
9714
|
-
continue;
|
|
9715
|
-
}
|
|
9716
|
-
const rates = await collectLendingRates(registry, chainKey, rpc, assetAddr);
|
|
9717
|
-
for (const r of rates) {
|
|
9718
|
-
if (r.supply_apy > 0) {
|
|
9719
|
-
allRates.push({ chain: chain.name, protocol: r.protocol, supply_apy: r.supply_apy, borrow_variable_apy: r.borrow_variable_apy });
|
|
9720
|
-
}
|
|
9721
|
-
}
|
|
9722
|
-
} catch {
|
|
9723
|
-
}
|
|
9724
|
-
}
|
|
9725
|
-
allRates.sort((a, b) => b.supply_apy - a.supply_apy);
|
|
9726
|
-
const best = allRates[0] ? `${allRates[0].protocol} on ${allRates[0].chain}` : null;
|
|
9727
|
-
printOutput({ asset, chains_scanned: registry.chains.size, rates: allRates, best_supply: best }, getOpts());
|
|
9728
|
-
} catch (err) {
|
|
9729
|
-
printOutput({ error: String(err) }, getOpts());
|
|
9730
|
-
}
|
|
9731
|
-
});
|
|
9732
9759
|
yieldCmd.command("compare").description("Compare lending rates across protocols for an asset").option("--asset <token>", "Token symbol or address", "USDC").action(async (opts) => {
|
|
9733
9760
|
try {
|
|
9734
9761
|
const registry = Registry.loadEmbedded();
|
|
@@ -10323,7 +10350,7 @@ function decodeU2562(data, wordOffset = 0) {
|
|
|
10323
10350
|
}
|
|
10324
10351
|
function registerPortfolio(parent, getOpts) {
|
|
10325
10352
|
const portfolio = parent.command("portfolio").description("Aggregate positions across all protocols");
|
|
10326
|
-
portfolio.command("show").description("Show current portfolio positions").
|
|
10353
|
+
portfolio.command("show").description("Show current portfolio positions").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
10327
10354
|
const mode = getOpts();
|
|
10328
10355
|
const registry = Registry.loadEmbedded();
|
|
10329
10356
|
const chainName = requireChain(parent, getOpts);
|
|
@@ -10335,9 +10362,14 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10335
10362
|
printOutput({ error: `Chain not found: ${chainName}` }, mode);
|
|
10336
10363
|
return;
|
|
10337
10364
|
}
|
|
10338
|
-
const
|
|
10365
|
+
const addr = opts.address ?? process.env["DEFI_WALLET_ADDRESS"];
|
|
10366
|
+
if (!addr) {
|
|
10367
|
+
printOutput({ error: "--address required (or set DEFI_WALLET_ADDRESS)" }, mode);
|
|
10368
|
+
return;
|
|
10369
|
+
}
|
|
10370
|
+
const user = addr;
|
|
10339
10371
|
if (!/^0x[0-9a-fA-F]{40}$/.test(user)) {
|
|
10340
|
-
printOutput({ error: `Invalid address: ${
|
|
10372
|
+
printOutput({ error: `Invalid address: ${addr}` }, mode);
|
|
10341
10373
|
return;
|
|
10342
10374
|
}
|
|
10343
10375
|
const rpc = chain.effectiveRpcUrl();
|
|
@@ -10463,17 +10495,22 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10463
10495
|
mode
|
|
10464
10496
|
);
|
|
10465
10497
|
});
|
|
10466
|
-
portfolio.command("snapshot").description("Take a new portfolio snapshot and save it locally").
|
|
10498
|
+
portfolio.command("snapshot").description("Take a new portfolio snapshot and save it locally").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
10467
10499
|
const mode = getOpts();
|
|
10468
10500
|
const chainName = requireChain(parent, getOpts);
|
|
10469
10501
|
if (!chainName) return;
|
|
10470
10502
|
const registry = Registry.loadEmbedded();
|
|
10471
|
-
|
|
10472
|
-
|
|
10503
|
+
const addr = opts.address ?? process.env["DEFI_WALLET_ADDRESS"];
|
|
10504
|
+
if (!addr) {
|
|
10505
|
+
printOutput({ error: "--address required (or set DEFI_WALLET_ADDRESS)" }, mode);
|
|
10506
|
+
return;
|
|
10507
|
+
}
|
|
10508
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(addr)) {
|
|
10509
|
+
printOutput({ error: `Invalid address: ${addr}` }, mode);
|
|
10473
10510
|
return;
|
|
10474
10511
|
}
|
|
10475
10512
|
try {
|
|
10476
|
-
const snapshot = await takeSnapshot(chainName,
|
|
10513
|
+
const snapshot = await takeSnapshot(chainName, addr, registry);
|
|
10477
10514
|
const filepath = saveSnapshot(snapshot);
|
|
10478
10515
|
printOutput(
|
|
10479
10516
|
{
|
|
@@ -10491,16 +10528,21 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10491
10528
|
printOutput({ error: errMsg(e) }, mode);
|
|
10492
10529
|
}
|
|
10493
10530
|
});
|
|
10494
|
-
portfolio.command("pnl").description("Show PnL since the last snapshot").
|
|
10531
|
+
portfolio.command("pnl").description("Show PnL since the last snapshot").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").option("--since <hours>", "Compare against snapshot from N hours ago (default: last snapshot)").action(async (opts) => {
|
|
10495
10532
|
const mode = getOpts();
|
|
10496
10533
|
const chainName = requireChain(parent, getOpts);
|
|
10497
10534
|
if (!chainName) return;
|
|
10498
10535
|
const registry = Registry.loadEmbedded();
|
|
10499
|
-
|
|
10500
|
-
|
|
10536
|
+
const addr = opts.address ?? process.env["DEFI_WALLET_ADDRESS"];
|
|
10537
|
+
if (!addr) {
|
|
10538
|
+
printOutput({ error: "--address required (or set DEFI_WALLET_ADDRESS)" }, mode);
|
|
10539
|
+
return;
|
|
10540
|
+
}
|
|
10541
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(addr)) {
|
|
10542
|
+
printOutput({ error: `Invalid address: ${addr}` }, mode);
|
|
10501
10543
|
return;
|
|
10502
10544
|
}
|
|
10503
|
-
const snapshots = loadSnapshots(chainName,
|
|
10545
|
+
const snapshots = loadSnapshots(chainName, addr, 50);
|
|
10504
10546
|
if (snapshots.length === 0) {
|
|
10505
10547
|
printOutput({ error: "No snapshots found. Run `portfolio snapshot` first." }, mode);
|
|
10506
10548
|
return;
|
|
@@ -10517,12 +10559,12 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10517
10559
|
previous = match;
|
|
10518
10560
|
}
|
|
10519
10561
|
try {
|
|
10520
|
-
const current = await takeSnapshot(chainName,
|
|
10562
|
+
const current = await takeSnapshot(chainName, addr, registry);
|
|
10521
10563
|
const pnl = calculatePnL(current, previous);
|
|
10522
10564
|
printOutput(
|
|
10523
10565
|
{
|
|
10524
10566
|
chain: chainName,
|
|
10525
|
-
wallet:
|
|
10567
|
+
wallet: addr,
|
|
10526
10568
|
previous_snapshot: new Date(previous.timestamp).toISOString(),
|
|
10527
10569
|
current_time: new Date(current.timestamp).toISOString(),
|
|
10528
10570
|
...pnl,
|
|
@@ -10537,16 +10579,21 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10537
10579
|
printOutput({ error: errMsg(e) }, mode);
|
|
10538
10580
|
}
|
|
10539
10581
|
});
|
|
10540
|
-
portfolio.command("history").description("List saved portfolio snapshots with values").
|
|
10582
|
+
portfolio.command("history").description("List saved portfolio snapshots with values").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").option("--limit <n>", "Number of snapshots to show", "10").action(async (opts) => {
|
|
10541
10583
|
const mode = getOpts();
|
|
10542
10584
|
const chainName = requireChain(parent, getOpts);
|
|
10543
10585
|
if (!chainName) return;
|
|
10544
|
-
|
|
10545
|
-
|
|
10586
|
+
const addr = opts.address ?? process.env["DEFI_WALLET_ADDRESS"];
|
|
10587
|
+
if (!addr) {
|
|
10588
|
+
printOutput({ error: "--address required (or set DEFI_WALLET_ADDRESS)" }, mode);
|
|
10589
|
+
return;
|
|
10590
|
+
}
|
|
10591
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(addr)) {
|
|
10592
|
+
printOutput({ error: `Invalid address: ${addr}` }, mode);
|
|
10546
10593
|
return;
|
|
10547
10594
|
}
|
|
10548
10595
|
const limit = parseInt(opts.limit, 10);
|
|
10549
|
-
const snapshots = loadSnapshots(chainName,
|
|
10596
|
+
const snapshots = loadSnapshots(chainName, addr, limit);
|
|
10550
10597
|
if (snapshots.length === 0) {
|
|
10551
10598
|
printOutput({ message: "No snapshots found for this address on this chain." }, mode);
|
|
10552
10599
|
return;
|
|
@@ -10709,16 +10756,21 @@ init_dist();
|
|
|
10709
10756
|
import { createPublicClient as createPublicClient24, http as http24, formatEther } from "viem";
|
|
10710
10757
|
function registerWallet(parent, getOpts) {
|
|
10711
10758
|
const wallet = parent.command("wallet").description("Wallet management");
|
|
10712
|
-
wallet.command("balance").description("Show native token balance").
|
|
10759
|
+
wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
10713
10760
|
const chainName = requireChain(parent, getOpts);
|
|
10714
10761
|
if (!chainName) return;
|
|
10762
|
+
const addr = opts.address ?? process.env["DEFI_WALLET_ADDRESS"];
|
|
10763
|
+
if (!addr) {
|
|
10764
|
+
printOutput({ error: "--address required (or set DEFI_WALLET_ADDRESS)" }, getOpts());
|
|
10765
|
+
return;
|
|
10766
|
+
}
|
|
10715
10767
|
const registry = Registry.loadEmbedded();
|
|
10716
10768
|
const chain = registry.getChain(chainName);
|
|
10717
10769
|
const client = createPublicClient24({ transport: http24(chain.effectiveRpcUrl()) });
|
|
10718
|
-
const balance = await client.getBalance({ address:
|
|
10770
|
+
const balance = await client.getBalance({ address: addr });
|
|
10719
10771
|
printOutput({
|
|
10720
10772
|
chain: chain.name,
|
|
10721
|
-
address:
|
|
10773
|
+
address: addr,
|
|
10722
10774
|
native_token: chain.native_token,
|
|
10723
10775
|
balance_wei: balance,
|
|
10724
10776
|
balance_formatted: formatEther(balance)
|
|
@@ -10735,22 +10787,27 @@ init_dist();
|
|
|
10735
10787
|
import { createPublicClient as createPublicClient25, http as http25, maxUint256 } from "viem";
|
|
10736
10788
|
function registerToken(parent, getOpts, makeExecutor2) {
|
|
10737
10789
|
const token = parent.command("token").description("Token operations: approve, allowance, transfer, balance");
|
|
10738
|
-
token.command("balance").description("Query token balance for an address").requiredOption("--token <token>", "Token symbol or address").
|
|
10790
|
+
token.command("balance").description("Query token balance for an address").requiredOption("--token <token>", "Token symbol or address").option("--owner <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
10739
10791
|
const chainName = requireChain(parent, getOpts);
|
|
10740
10792
|
if (!chainName) return;
|
|
10793
|
+
const owner = opts.owner ?? process.env["DEFI_WALLET_ADDRESS"];
|
|
10794
|
+
if (!owner) {
|
|
10795
|
+
printOutput({ error: "--owner required (or set DEFI_WALLET_ADDRESS)" }, getOpts());
|
|
10796
|
+
return;
|
|
10797
|
+
}
|
|
10741
10798
|
const registry = Registry.loadEmbedded();
|
|
10742
10799
|
const chain = registry.getChain(chainName);
|
|
10743
10800
|
const client = createPublicClient25({ transport: http25(chain.effectiveRpcUrl()) });
|
|
10744
10801
|
const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
|
|
10745
10802
|
const [balance, symbol, decimals] = await Promise.all([
|
|
10746
|
-
client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "balanceOf", args: [
|
|
10803
|
+
client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "balanceOf", args: [owner] }),
|
|
10747
10804
|
client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "symbol" }),
|
|
10748
10805
|
client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "decimals" })
|
|
10749
10806
|
]);
|
|
10750
10807
|
printOutput({
|
|
10751
10808
|
token: tokenAddr,
|
|
10752
10809
|
symbol,
|
|
10753
|
-
owner
|
|
10810
|
+
owner,
|
|
10754
10811
|
balance,
|
|
10755
10812
|
decimals
|
|
10756
10813
|
}, getOpts());
|
|
@@ -10766,9 +10823,14 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
10766
10823
|
const result = await executor.execute(tx);
|
|
10767
10824
|
printOutput(result, getOpts());
|
|
10768
10825
|
});
|
|
10769
|
-
token.command("allowance").description("Check token allowance").requiredOption("--token <token>", "Token symbol or address").
|
|
10826
|
+
token.command("allowance").description("Check token allowance").requiredOption("--token <token>", "Token symbol or address").option("--owner <address>", "Owner address (defaults to DEFI_WALLET_ADDRESS)").requiredOption("--spender <address>", "Spender address").action(async (opts) => {
|
|
10770
10827
|
const chainName = requireChain(parent, getOpts);
|
|
10771
10828
|
if (!chainName) return;
|
|
10829
|
+
const owner = opts.owner ?? process.env["DEFI_WALLET_ADDRESS"];
|
|
10830
|
+
if (!owner) {
|
|
10831
|
+
printOutput({ error: "--owner required (or set DEFI_WALLET_ADDRESS)" }, getOpts());
|
|
10832
|
+
return;
|
|
10833
|
+
}
|
|
10772
10834
|
const registry = Registry.loadEmbedded();
|
|
10773
10835
|
const chain = registry.getChain(chainName);
|
|
10774
10836
|
const client = createPublicClient25({ transport: http25(chain.effectiveRpcUrl()) });
|
|
@@ -10777,9 +10839,9 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
10777
10839
|
address: tokenAddr,
|
|
10778
10840
|
abi: erc20Abi,
|
|
10779
10841
|
functionName: "allowance",
|
|
10780
|
-
args: [
|
|
10842
|
+
args: [owner, opts.spender]
|
|
10781
10843
|
});
|
|
10782
|
-
printOutput({ token: tokenAddr, owner
|
|
10844
|
+
printOutput({ token: tokenAddr, owner, spender: opts.spender, allowance }, getOpts());
|
|
10783
10845
|
});
|
|
10784
10846
|
token.command("transfer").description("Transfer tokens to an address").requiredOption("--token <token>", "Token symbol or address").requiredOption("--to <address>", "Recipient address").requiredOption("--amount <amount>", "Amount to transfer (in wei)").action(async (opts) => {
|
|
10785
10847
|
const executor = makeExecutor2();
|
|
@@ -10798,6 +10860,29 @@ init_dist();
|
|
|
10798
10860
|
var LIFI_API = "https://li.quest/v1";
|
|
10799
10861
|
var DLN_API = "https://dln.debridge.finance/v1.0/dln/order";
|
|
10800
10862
|
var CCTP_FEE_API = "https://iris-api.circle.com/v2/burn/USDC/fees";
|
|
10863
|
+
var DEST_CHAIN_META = {
|
|
10864
|
+
ethereum: { chain_id: 1, name: "Ethereum" },
|
|
10865
|
+
optimism: { chain_id: 10, name: "Optimism" },
|
|
10866
|
+
polygon: { chain_id: 137, name: "Polygon" },
|
|
10867
|
+
arbitrum: { chain_id: 42161, name: "Arbitrum" },
|
|
10868
|
+
avalanche: { chain_id: 43114, name: "Avalanche" },
|
|
10869
|
+
linea: { chain_id: 59144, name: "Linea" },
|
|
10870
|
+
zksync: { chain_id: 324, name: "zkSync" }
|
|
10871
|
+
};
|
|
10872
|
+
function resolveDestChain(registry, slug) {
|
|
10873
|
+
try {
|
|
10874
|
+
const c = registry.getChain(slug);
|
|
10875
|
+
return { chain_id: c.chain_id, name: c.name };
|
|
10876
|
+
} catch {
|
|
10877
|
+
const meta = DEST_CHAIN_META[slug];
|
|
10878
|
+
if (!meta) {
|
|
10879
|
+
throw new Error(
|
|
10880
|
+
`Unknown destination chain '${slug}'. Source chains: hyperevm, mantle, base, bnb, monad. Bridge destinations also include: ${Object.keys(DEST_CHAIN_META).join(", ")}.`
|
|
10881
|
+
);
|
|
10882
|
+
}
|
|
10883
|
+
return meta;
|
|
10884
|
+
}
|
|
10885
|
+
}
|
|
10801
10886
|
var DLN_CHAIN_IDS = {
|
|
10802
10887
|
ethereum: 1,
|
|
10803
10888
|
optimism: 10,
|
|
@@ -10896,7 +10981,13 @@ function registerBridge(parent, getOpts) {
|
|
|
10896
10981
|
if (!chainName) return;
|
|
10897
10982
|
const registry = Registry.loadEmbedded();
|
|
10898
10983
|
const fromChain = registry.getChain(chainName);
|
|
10899
|
-
|
|
10984
|
+
let toChain;
|
|
10985
|
+
try {
|
|
10986
|
+
toChain = resolveDestChain(registry, opts.toChain);
|
|
10987
|
+
} catch (e) {
|
|
10988
|
+
printOutput({ error: errMsg(e) }, getOpts());
|
|
10989
|
+
return;
|
|
10990
|
+
}
|
|
10900
10991
|
const tokenAddr = opts.token.startsWith("0x") ? opts.token : registry.resolveToken(chainName, opts.token).address;
|
|
10901
10992
|
const recipient = resolveWallet(opts.recipient);
|
|
10902
10993
|
const provider = opts.provider.toLowerCase();
|
|
@@ -11653,7 +11744,17 @@ function handleOwsError(e, getOpts) {
|
|
|
11653
11744
|
// src/cli.ts
|
|
11654
11745
|
var _require2 = createRequire2(import.meta.url);
|
|
11655
11746
|
var _pkg = _require2("../package.json");
|
|
11656
|
-
|
|
11747
|
+
function buildBanner() {
|
|
11748
|
+
let chainCount = 0;
|
|
11749
|
+
let protocolCount = 0;
|
|
11750
|
+
try {
|
|
11751
|
+
const reg = Registry.loadEmbedded();
|
|
11752
|
+
chainCount = reg.chains.size;
|
|
11753
|
+
protocolCount = reg.protocols.length;
|
|
11754
|
+
} catch {
|
|
11755
|
+
}
|
|
11756
|
+
const stats = chainCount && protocolCount ? `${chainCount} chains \xB7 ${protocolCount} protocols \xB7 by HypurrQuant` : `by HypurrQuant`;
|
|
11757
|
+
return `
|
|
11657
11758
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557
|
|
11658
11759
|
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
11659
11760
|
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
@@ -11661,11 +11762,13 @@ var BANNER = `
|
|
|
11661
11762
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551
|
|
11662
11763
|
\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D
|
|
11663
11764
|
|
|
11664
|
-
|
|
11765
|
+
${stats}
|
|
11665
11766
|
|
|
11666
11767
|
Lending, LP farming, DEX swap, yield comparison
|
|
11667
11768
|
\u2014 all from your terminal.
|
|
11668
11769
|
`;
|
|
11770
|
+
}
|
|
11771
|
+
var BANNER = buildBanner();
|
|
11669
11772
|
var program = new Command().name("defi").description("DeFi CLI \u2014 Multi-chain DeFi toolkit").version(_pkg.version).addHelpText("before", BANNER).option("--json", "Output as JSON").option("--ndjson", "Output as newline-delimited JSON").option("--fields <fields>", "Select specific output fields (comma-separated)").option("--chain <chain>", "Target chain").option("--dry-run", "Dry-run mode (default, no broadcast)", true).option("--broadcast", "Actually broadcast the transaction");
|
|
11670
11773
|
function getOutputMode() {
|
|
11671
11774
|
const opts = program.opts();
|
|
@@ -11699,8 +11802,13 @@ registerOws(program, getOutputMode);
|
|
|
11699
11802
|
init_dist();
|
|
11700
11803
|
import pc4 from "picocolors";
|
|
11701
11804
|
import { encodeFunctionData as encodeFunctionData30, parseAbi as parseAbi34, formatUnits } from "viem";
|
|
11702
|
-
var
|
|
11703
|
-
|
|
11805
|
+
var DASHBOARD_CHAINS = [
|
|
11806
|
+
{ slug: "hyperevm", tokens: ["HYPE", "WHYPE", "USDC", "USDT0", "USDe", "kHYPE", "wstHYPE"] },
|
|
11807
|
+
{ slug: "mantle", tokens: ["MNT", "WMNT", "USDC", "USDT", "WETH", "mETH"] },
|
|
11808
|
+
{ slug: "base", tokens: ["ETH", "WETH", "USDC", "AERO"] },
|
|
11809
|
+
{ slug: "bnb", tokens: ["BNB", "WBNB", "USDT", "USDC", "BUSD", "CAKE"] },
|
|
11810
|
+
{ slug: "monad", tokens: ["MON", "WMON", "USDC", "USDT0", "WETH", "WBTC"] }
|
|
11811
|
+
];
|
|
11704
11812
|
var balanceOfAbi = parseAbi34([
|
|
11705
11813
|
"function balanceOf(address account) view returns (uint256)"
|
|
11706
11814
|
]);
|
|
@@ -11713,20 +11821,12 @@ async function fetchBalances(rpcUrl, wallet, tokens) {
|
|
|
11713
11821
|
if (isNative) {
|
|
11714
11822
|
return [
|
|
11715
11823
|
MULTICALL3_ADDRESS,
|
|
11716
|
-
encodeFunctionData30({
|
|
11717
|
-
abi: getEthBalanceAbi,
|
|
11718
|
-
functionName: "getEthBalance",
|
|
11719
|
-
args: [wallet]
|
|
11720
|
-
})
|
|
11824
|
+
encodeFunctionData30({ abi: getEthBalanceAbi, functionName: "getEthBalance", args: [wallet] })
|
|
11721
11825
|
];
|
|
11722
11826
|
}
|
|
11723
11827
|
return [
|
|
11724
11828
|
t.address,
|
|
11725
|
-
encodeFunctionData30({
|
|
11726
|
-
abi: balanceOfAbi,
|
|
11727
|
-
functionName: "balanceOf",
|
|
11728
|
-
args: [wallet]
|
|
11729
|
-
})
|
|
11829
|
+
encodeFunctionData30({ abi: balanceOfAbi, functionName: "balanceOf", args: [wallet] })
|
|
11730
11830
|
];
|
|
11731
11831
|
});
|
|
11732
11832
|
let results;
|
|
@@ -11758,6 +11858,27 @@ function formatBalanceLine(sym, bal) {
|
|
|
11758
11858
|
const balPad = padLeft(bal, 12);
|
|
11759
11859
|
return ` ${symPad}${balPad}`;
|
|
11760
11860
|
}
|
|
11861
|
+
async function resolveChainBalances(registry, wallet) {
|
|
11862
|
+
const chains = DASHBOARD_CHAINS.map(({ slug, tokens: order }) => {
|
|
11863
|
+
const chain = registry.getChain(slug);
|
|
11864
|
+
const allTokens = registry.tokens.get(slug) ?? [];
|
|
11865
|
+
const sorted = order.map((s) => allTokens.find((t) => t.symbol === s)).filter(Boolean);
|
|
11866
|
+
return { slug, chain, tokens: sorted };
|
|
11867
|
+
});
|
|
11868
|
+
const balanceLists = await Promise.all(
|
|
11869
|
+
chains.map(
|
|
11870
|
+
({ chain, tokens }) => fetchBalances(chain.effectiveRpcUrl(), wallet, tokens).catch(
|
|
11871
|
+
() => tokens.map((t) => ({ symbol: t.symbol, balance: "?", decimals: t.decimals }))
|
|
11872
|
+
)
|
|
11873
|
+
)
|
|
11874
|
+
);
|
|
11875
|
+
return chains.map((c, i) => ({
|
|
11876
|
+
slug: c.slug,
|
|
11877
|
+
name: c.chain.name,
|
|
11878
|
+
tokens: c.tokens,
|
|
11879
|
+
balances: balanceLists[i] ?? []
|
|
11880
|
+
}));
|
|
11881
|
+
}
|
|
11761
11882
|
async function showLandingPage(isJson) {
|
|
11762
11883
|
const registry = Registry.loadEmbedded();
|
|
11763
11884
|
const wallet = process.env.DEFI_WALLET_ADDRESS;
|
|
@@ -11766,23 +11887,10 @@ async function showLandingPage(isJson) {
|
|
|
11766
11887
|
console.log(JSON.stringify({ error: "DEFI_WALLET_ADDRESS not set" }, null, 2));
|
|
11767
11888
|
return;
|
|
11768
11889
|
}
|
|
11769
|
-
const
|
|
11770
|
-
const
|
|
11771
|
-
const
|
|
11772
|
-
|
|
11773
|
-
const heSorted2 = HYPEREVM_DISPLAY.map((s) => heTokens2.find((t) => t.symbol === s)).filter(Boolean);
|
|
11774
|
-
const mantleSorted2 = MANTLE_DISPLAY.map((s) => mantleTokens2.find((t) => t.symbol === s)).filter(Boolean);
|
|
11775
|
-
const [heBalances2, mantleBalances2] = await Promise.all([
|
|
11776
|
-
fetchBalances(heChain2.effectiveRpcUrl(), wallet, heSorted2),
|
|
11777
|
-
fetchBalances(mantleChain2.effectiveRpcUrl(), wallet, mantleSorted2)
|
|
11778
|
-
]);
|
|
11779
|
-
console.log(JSON.stringify({
|
|
11780
|
-
wallet,
|
|
11781
|
-
chains: {
|
|
11782
|
-
hyperevm: { name: heChain2.name, balances: heBalances2 },
|
|
11783
|
-
mantle: { name: mantleChain2.name, balances: mantleBalances2 }
|
|
11784
|
-
}
|
|
11785
|
-
}, null, 2));
|
|
11890
|
+
const resolved2 = await resolveChainBalances(registry, wallet);
|
|
11891
|
+
const chains = {};
|
|
11892
|
+
for (const c of resolved2) chains[c.slug] = { name: c.name, balances: c.balances };
|
|
11893
|
+
console.log(JSON.stringify({ wallet, chains }, null, 2));
|
|
11786
11894
|
return;
|
|
11787
11895
|
}
|
|
11788
11896
|
const { createRequire: createRequire3 } = await import("module");
|
|
@@ -11808,51 +11916,38 @@ async function showLandingPage(isJson) {
|
|
|
11808
11916
|
console.log("");
|
|
11809
11917
|
return;
|
|
11810
11918
|
}
|
|
11811
|
-
const
|
|
11812
|
-
const mantleChain = registry.getChain("mantle");
|
|
11813
|
-
const heTokens = (registry.tokens.get("hyperevm") ?? []).filter((t) => HYPEREVM_DISPLAY.includes(t.symbol));
|
|
11814
|
-
const mantleTokens = (registry.tokens.get("mantle") ?? []).filter((t) => MANTLE_DISPLAY.includes(t.symbol));
|
|
11815
|
-
const heSorted = HYPEREVM_DISPLAY.map((s) => heTokens.find((t) => t.symbol === s)).filter(Boolean);
|
|
11816
|
-
const mantleSorted = MANTLE_DISPLAY.map((s) => mantleTokens.find((t) => t.symbol === s)).filter(Boolean);
|
|
11817
|
-
const [heBalances, mantleBalances] = await Promise.all([
|
|
11818
|
-
fetchBalances(heChain.effectiveRpcUrl(), wallet, heSorted).catch(
|
|
11819
|
-
() => heSorted.map((t) => ({ symbol: t.symbol, balance: "?", decimals: t.decimals }))
|
|
11820
|
-
),
|
|
11821
|
-
fetchBalances(mantleChain.effectiveRpcUrl(), wallet, mantleSorted).catch(
|
|
11822
|
-
() => mantleSorted.map((t) => ({ symbol: t.symbol, balance: "?", decimals: t.decimals }))
|
|
11823
|
-
)
|
|
11824
|
-
]);
|
|
11919
|
+
const resolved = await resolveChainBalances(registry, wallet);
|
|
11825
11920
|
const colWidth = 38;
|
|
11826
11921
|
const divider = "\u2500".repeat(colWidth - 2);
|
|
11922
|
+
const chainNames = resolved.map((c) => c.name).join(pc4.dim(" \xB7 "));
|
|
11827
11923
|
console.log("");
|
|
11828
|
-
console.log(
|
|
11829
|
-
pc4.bold(pc4.cyan(" DeFi CLI v" + version)) + pc4.dim(" \u2014 ") + pc4.bold(heChain.name) + pc4.dim(" \xB7 ") + pc4.bold(mantleChain.name)
|
|
11830
|
-
);
|
|
11924
|
+
console.log(pc4.bold(pc4.cyan(" DeFi CLI v" + version)) + pc4.dim(" \u2014 ") + pc4.bold(chainNames));
|
|
11831
11925
|
console.log("");
|
|
11832
11926
|
console.log(" Wallet: " + pc4.yellow(shortenAddress(wallet)));
|
|
11833
11927
|
console.log("");
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11928
|
+
for (let i = 0; i < resolved.length; i += 2) {
|
|
11929
|
+
const left = resolved[i];
|
|
11930
|
+
const right = resolved[i + 1];
|
|
11931
|
+
const leftHeader = padRight(" " + pc4.bold(left.name), colWidth + 10);
|
|
11932
|
+
const rightHeader = right ? pc4.bold(right.name) : "";
|
|
11933
|
+
console.log(leftHeader + (right ? " " + rightHeader : ""));
|
|
11934
|
+
const leftDivider = padRight(" " + pc4.dim(divider), colWidth + 10);
|
|
11935
|
+
const rightDivider = right ? pc4.dim(divider) : "";
|
|
11936
|
+
console.log(leftDivider + (right ? " " + rightDivider : ""));
|
|
11937
|
+
const maxRows = Math.max(left.balances.length, right?.balances.length ?? 0);
|
|
11938
|
+
for (let r = 0; r < maxRows; r++) {
|
|
11939
|
+
const lEntry = left.balances[r];
|
|
11940
|
+
const rEntry = right?.balances[r];
|
|
11941
|
+
const lText = lEntry ? formatBalanceLine(lEntry.symbol, lEntry.balance) : "";
|
|
11942
|
+
const rText = rEntry ? formatBalanceLine(rEntry.symbol, rEntry.balance) : "";
|
|
11943
|
+
const lColored = lEntry ? lEntry.balance === "0.00" || lEntry.balance === "?" ? pc4.dim(lText) : lText : "";
|
|
11944
|
+
const rColored = rEntry ? rEntry.balance === "0.00" || rEntry.balance === "?" ? pc4.dim(rText) : rText : "";
|
|
11945
|
+
const lVisible = lText.length;
|
|
11946
|
+
const lPad = colWidth - lVisible;
|
|
11947
|
+
const lPadded = lColored + (lPad > 0 ? " ".repeat(lPad) : "");
|
|
11948
|
+
console.log(lPadded + (right ? " " + rColored : ""));
|
|
11949
|
+
}
|
|
11950
|
+
if (i + 2 < resolved.length) console.log("");
|
|
11856
11951
|
}
|
|
11857
11952
|
console.log("");
|
|
11858
11953
|
console.log(" " + pc4.bold("Commands:"));
|