@hypurrquant/defi-cli 1.0.10 → 1.0.12
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/dist/index.js +335 -70
- package/dist/index.js.map +1 -1
- package/dist/main.js +335 -70
- package/dist/main.js.map +1 -1
- package/dist/mcp-server.js +31 -6
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/skills/defi-cli/SKILL.md +37 -1
- package/skills/defi-cli/package.json +1 -1
- package/skills/defi-cli/references/commands.md +39 -6
- package/skills/defi-cli/scripts/bridge-quote.sh +34 -0
- package/skills/defi-cli/scripts/lending-supply-flow.sh +28 -0
- package/skills/defi-cli/scripts/lp-claim-all.sh +25 -0
- package/skills/defi-cli/scripts/lp-emission-discover.sh +20 -0
- package/skills/defi-cli/scripts/portfolio-snapshot.sh +0 -0
- package/skills/defi-cli/scripts/preflight.sh +0 -0
- package/skills/defi-cli/scripts/swap-quote.sh +37 -0
- package/skills/defi-cli/scripts/wallet-status.sh +30 -0
- package/skills/defi-cli/scripts/yield-scan.sh +0 -0
- package/skills/defi-cli/scripts/exploit-scan.sh +0 -10
package/dist/index.js
CHANGED
|
@@ -1968,12 +1968,16 @@ var init_dist2 = __esm({
|
|
|
1968
1968
|
functionName: "add_liquidity",
|
|
1969
1969
|
args: [[params.amount_a, params.amount_b], 0n]
|
|
1970
1970
|
});
|
|
1971
|
+
const approvals = [];
|
|
1972
|
+
if (params.amount_a > 0n) approvals.push({ token: params.token_a, spender: this.router, amount: params.amount_a });
|
|
1973
|
+
if (params.amount_b > 0n) approvals.push({ token: params.token_b, spender: this.router, amount: params.amount_b });
|
|
1971
1974
|
return {
|
|
1972
1975
|
description: `[${this.protocolName}] Curve add liquidity`,
|
|
1973
1976
|
to: this.router,
|
|
1974
1977
|
data,
|
|
1975
1978
|
value: 0n,
|
|
1976
|
-
gas_estimate: 4e5
|
|
1979
|
+
gas_estimate: 4e5,
|
|
1980
|
+
approvals
|
|
1977
1981
|
};
|
|
1978
1982
|
}
|
|
1979
1983
|
async buildRemoveLiquidity(params) {
|
|
@@ -7333,11 +7337,29 @@ var Executor = class _Executor {
|
|
|
7333
7337
|
static applyGasBuffer(gas) {
|
|
7334
7338
|
return gas * GAS_BUFFER_BPS / 10000n;
|
|
7335
7339
|
}
|
|
7340
|
+
/**
|
|
7341
|
+
* EIP-1559 max-fee formula: `baseFee * 1.25 + priorityFee`.
|
|
7342
|
+
* Extracted as a static so the math is unit-testable without mocking viem.
|
|
7343
|
+
*/
|
|
7344
|
+
static computeMaxFee(baseFeePerGas, priorityFee) {
|
|
7345
|
+
return baseFeePerGas * 125n / 100n + priorityFee;
|
|
7346
|
+
}
|
|
7336
7347
|
/**
|
|
7337
7348
|
* Check allowance for a single token/spender pair and send an approve tx if needed.
|
|
7338
7349
|
* Only called in broadcast mode (not dry-run).
|
|
7339
7350
|
*/
|
|
7351
|
+
/**
|
|
7352
|
+
* Recognize the standard native-token sentinels:
|
|
7353
|
+
* - 0x0000…0000 — defi-cli's internal marker for native gas tokens
|
|
7354
|
+
* - 0xeeee…eeee — 1inch / KyberSwap / OpenOcean canonical sentinel
|
|
7355
|
+
* Any address normalised to lowercase matches case-insensitively.
|
|
7356
|
+
*/
|
|
7357
|
+
static isNativeSentinel(token) {
|
|
7358
|
+
const t = token.toLowerCase();
|
|
7359
|
+
return t === "0x0000000000000000000000000000000000000000" || t === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
7360
|
+
}
|
|
7340
7361
|
async checkAndApprove(token, spender, amount, owner, publicClient, walletClient) {
|
|
7362
|
+
if (_Executor.isNativeSentinel(token)) return;
|
|
7341
7363
|
const allowance = await publicClient.readContract({
|
|
7342
7364
|
address: token,
|
|
7343
7365
|
abi: ERC20_ABI,
|
|
@@ -7418,9 +7440,12 @@ var Executor = class _Executor {
|
|
|
7418
7440
|
/**
|
|
7419
7441
|
* Fetch EIP-1559 fee params. Returns [maxFeePerGas, maxPriorityFeePerGas].
|
|
7420
7442
|
*
|
|
7421
|
-
* Strategy: read the latest block's `baseFeePerGas` and
|
|
7422
|
-
*
|
|
7423
|
-
*
|
|
7443
|
+
* Strategy: read the latest block's `baseFeePerGas` and apply the conservative
|
|
7444
|
+
* formula `maxFee = baseFee * 1.25 + priorityFee` (one block of head-room — the
|
|
7445
|
+
* 12.5% per-block max bump). The canonical 2× wastes budget on chains where
|
|
7446
|
+
* baseFee is already elevated (e.g., Mantle ~50 gwei → 100 gwei doubled the
|
|
7447
|
+
* MNT requirement and broke multi-step flows). Falls back to
|
|
7448
|
+
* `getGasPrice() + priorityFee` only when
|
|
7424
7449
|
* the chain doesn't expose `baseFeePerGas` (pre-1559).
|
|
7425
7450
|
*
|
|
7426
7451
|
* Why not gasPrice * 2: `getGasPrice()` returns `baseFee + priorityFee`, so
|
|
@@ -7439,8 +7464,7 @@ var Executor = class _Executor {
|
|
|
7439
7464
|
try {
|
|
7440
7465
|
const block = await client.getBlock({ blockTag: "latest" });
|
|
7441
7466
|
if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
|
|
7442
|
-
|
|
7443
|
-
return [maxFee, priorityFee];
|
|
7467
|
+
return [_Executor.computeMaxFee(block.baseFeePerGas, priorityFee), priorityFee];
|
|
7444
7468
|
}
|
|
7445
7469
|
} catch {
|
|
7446
7470
|
}
|
|
@@ -7483,6 +7507,7 @@ var Executor = class _Executor {
|
|
|
7483
7507
|
if (tx.approvals && tx.approvals.length > 0) {
|
|
7484
7508
|
const pendingApprovals = [];
|
|
7485
7509
|
for (const approval of tx.approvals) {
|
|
7510
|
+
if (_Executor.isNativeSentinel(approval.token)) continue;
|
|
7486
7511
|
try {
|
|
7487
7512
|
const allowance = await client.readContract({
|
|
7488
7513
|
address: approval.token,
|
|
@@ -8261,7 +8286,8 @@ function handleSchema(params) {
|
|
|
8261
8286
|
function registerSchema(parent, getOpts) {
|
|
8262
8287
|
parent.command("schema [command]").description("Output JSON schema for a command (agent-friendly)").option("--all", "Show all schemas").action(async (command, opts) => {
|
|
8263
8288
|
const mode = getOpts();
|
|
8264
|
-
const
|
|
8289
|
+
const raw = opts.all ? "all" : command ?? "all";
|
|
8290
|
+
const action = raw.replace(/-/g, ".");
|
|
8265
8291
|
const params = { action };
|
|
8266
8292
|
const schema = handleSchema(params);
|
|
8267
8293
|
printOutput(schema, mode);
|
|
@@ -8452,6 +8478,11 @@ function buildPipelineSteps(p, input = {}) {
|
|
|
8452
8478
|
["--protocol", slug, "slug"],
|
|
8453
8479
|
["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"]
|
|
8454
8480
|
]);
|
|
8481
|
+
const claimAutoStakeNftGauge = () => `defi ${chainFlag}lp claim ` + buildCmd([
|
|
8482
|
+
["--protocol", slug, "slug"],
|
|
8483
|
+
["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"],
|
|
8484
|
+
["--token-id", input.tokenId, "token-id-from-mint-result"]
|
|
8485
|
+
]);
|
|
8455
8486
|
switch (p.reward_strategy) {
|
|
8456
8487
|
case "lp_fee_only":
|
|
8457
8488
|
return [
|
|
@@ -8476,11 +8507,18 @@ function buildPipelineSteps(p, input = {}) {
|
|
|
8476
8507
|
{ step: "stake", function: "gauge.deposit(amount)", cli_command: baseFarm },
|
|
8477
8508
|
{ step: "claim", function: "gauge.earned(token, account) \u2192 gauge.getReward(account, tokens[])", cli_command: claimWithGauge() }
|
|
8478
8509
|
];
|
|
8479
|
-
case "auto_stake":
|
|
8510
|
+
case "auto_stake": {
|
|
8511
|
+
const isNftAutoStake = p.interface === "uniswap_v3";
|
|
8480
8512
|
return [
|
|
8481
8513
|
{ step: "mint", function: "Router.addLiquidity / NPM.mint", note: "LP automatically receives x(3,3) emissions \u2014 no separate stake step", cli_command: baseAdd },
|
|
8482
|
-
{
|
|
8514
|
+
{
|
|
8515
|
+
step: "claim",
|
|
8516
|
+
function: isNftAutoStake ? "NPM.getPeriodReward(currentEpoch, tokenId, tokens[], receiver)" : "gauge.getReward(account, tokens[])",
|
|
8517
|
+
note: isNftAutoStake ? "Ramses CL: claim via NPM with --token-id; gauge.getReward* reverts NOT_AUTHORIZED_CLAIMER for EOAs" : "Multi-token reward (xRAM + WHYPE on Ramses HL)",
|
|
8518
|
+
cli_command: isNftAutoStake ? claimAutoStakeNftGauge() : claimWithGauge()
|
|
8519
|
+
}
|
|
8483
8520
|
];
|
|
8521
|
+
}
|
|
8484
8522
|
case "on_chain_masterchef":
|
|
8485
8523
|
return [
|
|
8486
8524
|
{ step: "mint", function: "NPM.mint or pool.mint", cli_command: baseAdd },
|
|
@@ -9191,7 +9229,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9191
9229
|
note: "Plan output. Run each cli_command sequentially. After the mint step, broadcast mode prints `details.minted_token_id` \u2014 feed that into the next step's --token-id."
|
|
9192
9230
|
}, getOpts());
|
|
9193
9231
|
});
|
|
9194
|
-
lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").
|
|
9232
|
+
lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").option("--token-a <token>", "First token symbol or address (required for V2/Curve/LB)").option("--token-b <token>", "Second token symbol or address (required for V2/Curve/LB)").option("--liquidity <amount>", "Liquidity amount to remove in wei (required for V2/Curve/LB)").option("--pool <address>", "Pool address (needed to resolve gauge)").option("--gauge <address>", "Gauge contract address (for solidly/hybra unstake)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--recipient <address>", "Recipient address").option("--redeem-type <n>", "Hybra: 0=instant exit (with penalty), 1=lock into 2-year veHYBR (default \u2014 WARNING: long lock)").option("--bins <binIds>", "Merchant Moe LB: comma-separated bin IDs to withdraw").option("--amounts <wei>", "Merchant Moe LB: comma-separated bin amounts (parallel to --bins, default: full balance)").action(async (opts) => {
|
|
9195
9233
|
const executor = makeExecutor2();
|
|
9196
9234
|
const chainName = parent.opts().chain;
|
|
9197
9235
|
if (!chainName) {
|
|
@@ -9207,6 +9245,9 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9207
9245
|
if (iface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
9208
9246
|
if (!opts.pool) throw new Error(`--pool is required for ${protocol.name} (Liquidity Book \u2014 pass --pool <addr>)`);
|
|
9209
9247
|
if (!opts.bins) throw new Error("--bins <id1,id2,...> is required for Merchant Moe LB remove");
|
|
9248
|
+
if (!opts.tokenA || !opts.tokenB) {
|
|
9249
|
+
throw new Error(`--token-a and --token-b are required for ${protocol.name} (Liquidity Book) remove`);
|
|
9250
|
+
}
|
|
9210
9251
|
const lbAdapter = createMerchantMoeLB(protocol, rpcUrl);
|
|
9211
9252
|
const tokenA2 = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
|
|
9212
9253
|
const tokenB2 = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
|
|
@@ -9249,8 +9290,22 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9249
9290
|
printOutput({ step: "lb_remove", ...result }, getOpts());
|
|
9250
9291
|
return;
|
|
9251
9292
|
}
|
|
9252
|
-
const
|
|
9253
|
-
const
|
|
9293
|
+
const NFT_REMOVE_IFACES = /* @__PURE__ */ new Set(["uniswap_v3", "algebra_v3", "thena_cl", "hybra"]);
|
|
9294
|
+
const isNftRemove = !!opts.tokenId && NFT_REMOVE_IFACES.has(iface);
|
|
9295
|
+
if (!isNftRemove) {
|
|
9296
|
+
const missing = [];
|
|
9297
|
+
if (!opts.tokenA) missing.push("--token-a");
|
|
9298
|
+
if (!opts.tokenB) missing.push("--token-b");
|
|
9299
|
+
if (!opts.liquidity) missing.push("--liquidity");
|
|
9300
|
+
if (missing.length > 0) {
|
|
9301
|
+
printOutput({
|
|
9302
|
+
error: `${missing.join(", ")} required for ${protocol.name} remove (or pass --token-id for V3/CL NFT-based remove).`
|
|
9303
|
+
}, getOpts());
|
|
9304
|
+
return;
|
|
9305
|
+
}
|
|
9306
|
+
}
|
|
9307
|
+
const tokenA = opts.tokenA ? opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address : void 0;
|
|
9308
|
+
const tokenB = opts.tokenB ? opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address : void 0;
|
|
9254
9309
|
const poolAddr = opts.pool ? opts.pool : void 0;
|
|
9255
9310
|
let didUnstake = false;
|
|
9256
9311
|
if (iface === "algebra_v3" && protocol.contracts?.["farming_center"] && opts.tokenId && poolAddr) {
|
|
@@ -9300,7 +9355,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9300
9355
|
if (iface === "hybra" && (!wOpts || wOpts.redeemType === 1)) {
|
|
9301
9356
|
process.stderr.write("WARNING: Hybra default redeemType=1 locks rewards into 2-year veHYBR NFT. Pass --redeem-type 0 for instant exit (with penalty).\n");
|
|
9302
9357
|
}
|
|
9303
|
-
const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, BigInt(opts.liquidity), tokenId, wOpts);
|
|
9358
|
+
const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, opts.liquidity ? BigInt(opts.liquidity) : 0n, tokenId, wOpts);
|
|
9304
9359
|
const withdrawResult = await executor.execute(withdrawTx);
|
|
9305
9360
|
printOutput({ step: "unstake_gauge", ...withdrawResult }, getOpts());
|
|
9306
9361
|
if (withdrawResult.status !== "confirmed" && withdrawResult.status !== "simulated") {
|
|
@@ -9315,11 +9370,31 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9315
9370
|
}
|
|
9316
9371
|
process.stderr.write("Step 2/2: Removing liquidity...\n");
|
|
9317
9372
|
const dexAdapter = createDex(protocol, rpcUrl);
|
|
9373
|
+
let removeLiquidity = opts.liquidity ? BigInt(opts.liquidity) : 0n;
|
|
9374
|
+
if (isNftRemove && removeLiquidity === 0n) {
|
|
9375
|
+
const npm = protocol.contracts?.["position_manager"];
|
|
9376
|
+
if (npm) {
|
|
9377
|
+
const c = createPublicClient23({ transport: http23(rpcUrl) });
|
|
9378
|
+
const pos = await detectV3Liquidity(c, npm, BigInt(opts.tokenId));
|
|
9379
|
+
if (pos) {
|
|
9380
|
+
removeLiquidity = pos.liquidity;
|
|
9381
|
+
process.stderr.write(` Read live liquidity ${removeLiquidity} from NPM.positions(${opts.tokenId}).
|
|
9382
|
+
`);
|
|
9383
|
+
}
|
|
9384
|
+
}
|
|
9385
|
+
}
|
|
9386
|
+
if (isNftRemove && removeLiquidity === 0n) {
|
|
9387
|
+
printOutput({
|
|
9388
|
+
error: `tokenId ${opts.tokenId} has zero liquidity (already removed?). Pass --liquidity explicitly to override, or pick a different tokenId.`
|
|
9389
|
+
}, getOpts());
|
|
9390
|
+
return;
|
|
9391
|
+
}
|
|
9392
|
+
const ZERO = "0x0000000000000000000000000000000000000000";
|
|
9318
9393
|
const removeTx = await dexAdapter.buildRemoveLiquidity({
|
|
9319
9394
|
protocol: protocol.name,
|
|
9320
|
-
token_a: tokenA,
|
|
9321
|
-
token_b: tokenB,
|
|
9322
|
-
liquidity:
|
|
9395
|
+
token_a: tokenA ?? ZERO,
|
|
9396
|
+
token_b: tokenB ?? ZERO,
|
|
9397
|
+
liquidity: removeLiquidity,
|
|
9323
9398
|
recipient,
|
|
9324
9399
|
token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0
|
|
9325
9400
|
});
|
|
@@ -9698,12 +9773,20 @@ function resolveTokenAddress(registry, chainName, tokenOrAddress) {
|
|
|
9698
9773
|
return registry.resolveToken(chainName, tokenOrAddress).address;
|
|
9699
9774
|
}
|
|
9700
9775
|
var FALLBACK_ADDRESS = "0x0000000000000000000000000000000000000001";
|
|
9776
|
+
var warnedFallback = false;
|
|
9701
9777
|
function resolveWallet(override) {
|
|
9702
9778
|
if (override) return override;
|
|
9703
9779
|
try {
|
|
9704
9780
|
const { address } = resolveWalletWithSigner();
|
|
9705
9781
|
return address;
|
|
9706
9782
|
} catch {
|
|
9783
|
+
if (!warnedFallback) {
|
|
9784
|
+
process.stderr.write(
|
|
9785
|
+
`WARNING: no wallet configured (set DEFI_WALLET_ADDRESS or DEFI_PRIVATE_KEY, or use --wallet <name>). Using placeholder ${FALLBACK_ADDRESS} for dry-run preview ONLY \u2014 do NOT pass --broadcast with this address.
|
|
9786
|
+
`
|
|
9787
|
+
);
|
|
9788
|
+
warnedFallback = true;
|
|
9789
|
+
}
|
|
9707
9790
|
return FALLBACK_ADDRESS;
|
|
9708
9791
|
}
|
|
9709
9792
|
}
|
|
@@ -9806,7 +9889,8 @@ function resolveAsset(registry, chain, asset) {
|
|
|
9806
9889
|
}
|
|
9807
9890
|
async function collectLendingRates(registry, chainName, rpc, assetAddr) {
|
|
9808
9891
|
const protos = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending);
|
|
9809
|
-
const
|
|
9892
|
+
const rates = [];
|
|
9893
|
+
const errors = [];
|
|
9810
9894
|
let first = true;
|
|
9811
9895
|
for (const proto of protos) {
|
|
9812
9896
|
if (!first) {
|
|
@@ -9815,19 +9899,22 @@ async function collectLendingRates(registry, chainName, rpc, assetAddr) {
|
|
|
9815
9899
|
first = false;
|
|
9816
9900
|
try {
|
|
9817
9901
|
const lending = createLending(proto, rpc);
|
|
9818
|
-
const
|
|
9819
|
-
|
|
9902
|
+
const r = await lending.getRates(assetAddr);
|
|
9903
|
+
rates.push(r);
|
|
9820
9904
|
} catch (err) {
|
|
9821
9905
|
process.stderr.write(`Warning: ${proto.name} rates unavailable: ${err}
|
|
9822
9906
|
`);
|
|
9907
|
+
errors.push({ protocol: proto.name, type: "lending_supply", reason: errMsg(err) });
|
|
9823
9908
|
}
|
|
9824
9909
|
}
|
|
9825
|
-
return
|
|
9910
|
+
return { rates, errors };
|
|
9826
9911
|
}
|
|
9827
9912
|
async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
9828
9913
|
const opportunities = [];
|
|
9829
|
-
const
|
|
9830
|
-
|
|
9914
|
+
const errors = [];
|
|
9915
|
+
const lendingResult = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
9916
|
+
errors.push(...lendingResult.errors);
|
|
9917
|
+
for (const r of lendingResult.rates) {
|
|
9831
9918
|
if (r.supply_apy > 0) {
|
|
9832
9919
|
opportunities.push({
|
|
9833
9920
|
protocol: r.protocol,
|
|
@@ -9853,7 +9940,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
|
9853
9940
|
utilization: rates.utilization
|
|
9854
9941
|
});
|
|
9855
9942
|
}
|
|
9856
|
-
} catch {
|
|
9943
|
+
} catch (e) {
|
|
9944
|
+
errors.push({ protocol: proto.name, type: "morpho_vault", reason: errMsg(e) });
|
|
9857
9945
|
}
|
|
9858
9946
|
}
|
|
9859
9947
|
}
|
|
@@ -9869,7 +9957,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
|
9869
9957
|
apy: info.apy ?? 0,
|
|
9870
9958
|
total_assets: info.total_assets.toString()
|
|
9871
9959
|
});
|
|
9872
|
-
} catch {
|
|
9960
|
+
} catch (e) {
|
|
9961
|
+
errors.push({ protocol: proto.name, type: "vault", reason: errMsg(e) });
|
|
9873
9962
|
}
|
|
9874
9963
|
}
|
|
9875
9964
|
}
|
|
@@ -9878,7 +9967,7 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
|
9878
9967
|
const ba = b["apy"] ?? 0;
|
|
9879
9968
|
return ba - aa;
|
|
9880
9969
|
});
|
|
9881
|
-
return opportunities;
|
|
9970
|
+
return { opportunities, errors };
|
|
9882
9971
|
}
|
|
9883
9972
|
async function runYieldScan(registry, asset, output) {
|
|
9884
9973
|
const t0 = Date.now();
|
|
@@ -10020,10 +10109,13 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10020
10109
|
const chain = registry.getChain(chainName);
|
|
10021
10110
|
const rpc = chain.effectiveRpcUrl();
|
|
10022
10111
|
const assetAddr = resolveAsset(registry, chainName, opts.asset);
|
|
10023
|
-
const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10112
|
+
const { rates: results, errors: ratesErrors } = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10024
10113
|
if (results.length === 0) {
|
|
10025
10114
|
printOutput(
|
|
10026
|
-
|
|
10115
|
+
ratesErrors.length > 0 ? {
|
|
10116
|
+
error: `Could not collect lending rates for '${opts.asset}': ${ratesErrors.length} probe(s) failed (likely RPC throttling). Retry, or set ${chainName.toUpperCase()}_RPC_URL.`,
|
|
10117
|
+
failed_probes: ratesErrors
|
|
10118
|
+
} : { error: `No lending rate data available for asset '${opts.asset}'` },
|
|
10027
10119
|
getOpts()
|
|
10028
10120
|
);
|
|
10029
10121
|
process.exit(1);
|
|
@@ -10265,9 +10357,16 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10265
10357
|
const assetAddr = resolveAsset(registry, chainName, asset);
|
|
10266
10358
|
const strategy = opts.strategy ?? "auto";
|
|
10267
10359
|
if (strategy === "auto") {
|
|
10268
|
-
const opportunities = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
|
|
10360
|
+
const { opportunities, errors } = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
|
|
10269
10361
|
if (opportunities.length === 0) {
|
|
10270
|
-
|
|
10362
|
+
if (errors.length > 0) {
|
|
10363
|
+
printOutput({
|
|
10364
|
+
error: `Could not collect yield data for '${asset}': ${errors.length} probe(s) failed (likely RPC throttling or transport error). Retry, or set a private RPC URL via ${chainName.toUpperCase()}_RPC_URL.`,
|
|
10365
|
+
failed_probes: errors
|
|
10366
|
+
}, getOpts());
|
|
10367
|
+
} else {
|
|
10368
|
+
printOutput({ error: `No yield opportunities found for '${asset}'` }, getOpts());
|
|
10369
|
+
}
|
|
10271
10370
|
process.exit(1);
|
|
10272
10371
|
return;
|
|
10273
10372
|
}
|
|
@@ -10297,9 +10396,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10297
10396
|
getOpts()
|
|
10298
10397
|
);
|
|
10299
10398
|
} else if (strategy === "best-supply") {
|
|
10300
|
-
const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10399
|
+
const { rates: results, errors: rErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10301
10400
|
if (results.length === 0) {
|
|
10302
|
-
printOutput(
|
|
10401
|
+
printOutput(
|
|
10402
|
+
rErr.length > 0 ? { error: `Could not collect lending rates for '${asset}': ${rErr.length} probe(s) failed (RPC throttling likely).`, failed_probes: rErr } : { error: `No lending rate data available for asset '${asset}'` },
|
|
10403
|
+
getOpts()
|
|
10404
|
+
);
|
|
10303
10405
|
process.exit(1);
|
|
10304
10406
|
return;
|
|
10305
10407
|
}
|
|
@@ -10322,9 +10424,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10322
10424
|
getOpts()
|
|
10323
10425
|
);
|
|
10324
10426
|
} else if (strategy === "leverage-loop") {
|
|
10325
|
-
const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10427
|
+
const { rates: results, errors: lErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10326
10428
|
if (results.length === 0) {
|
|
10327
|
-
printOutput(
|
|
10429
|
+
printOutput(
|
|
10430
|
+
lErr.length > 0 ? { error: `Could not collect lending rates for '${asset}': ${lErr.length} probe(s) failed (RPC throttling likely).`, failed_probes: lErr } : { error: `No lending rate data available for asset '${asset}'` },
|
|
10431
|
+
getOpts()
|
|
10432
|
+
);
|
|
10328
10433
|
process.exit(1);
|
|
10329
10434
|
return;
|
|
10330
10435
|
}
|
|
@@ -10388,14 +10493,14 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10388
10493
|
|
|
10389
10494
|
// src/commands/portfolio.ts
|
|
10390
10495
|
init_dist();
|
|
10391
|
-
import { encodeFunctionData as encodeFunctionData29, parseAbi as parseAbi33 } from "viem";
|
|
10496
|
+
import { createPublicClient as createPublicClient25, encodeFunctionData as encodeFunctionData29, http as http25, parseAbi as parseAbi33 } from "viem";
|
|
10392
10497
|
|
|
10393
10498
|
// src/portfolio-tracker.ts
|
|
10394
10499
|
init_dist();
|
|
10395
10500
|
import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
|
|
10396
10501
|
import { homedir } from "os";
|
|
10397
10502
|
import { resolve as resolve3 } from "path";
|
|
10398
|
-
import { encodeFunctionData as encodeFunctionData28, parseAbi as parseAbi31 } from "viem";
|
|
10503
|
+
import { createPublicClient as createPublicClient24, encodeFunctionData as encodeFunctionData28, http as http24, parseAbi as parseAbi31 } from "viem";
|
|
10399
10504
|
var ERC20_ABI4 = parseAbi31([
|
|
10400
10505
|
"function balanceOf(address owner) external view returns (uint256)"
|
|
10401
10506
|
]);
|
|
@@ -10446,7 +10551,16 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10446
10551
|
const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
|
|
10447
10552
|
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
10448
10553
|
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
10554
|
+
const priceTokens = [];
|
|
10449
10555
|
if (oracleAddr) {
|
|
10556
|
+
for (const t of tokenEntries) {
|
|
10557
|
+
calls.push([
|
|
10558
|
+
oracleAddr,
|
|
10559
|
+
encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [t.address] })
|
|
10560
|
+
]);
|
|
10561
|
+
callLabels.push(`price:${t.address}`);
|
|
10562
|
+
priceTokens.push(t.address);
|
|
10563
|
+
}
|
|
10450
10564
|
calls.push([
|
|
10451
10565
|
oracleAddr,
|
|
10452
10566
|
encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [wrappedNative] })
|
|
@@ -10457,10 +10571,19 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10457
10571
|
if (calls.length > 0) {
|
|
10458
10572
|
results = await multicallRead(rpc, calls);
|
|
10459
10573
|
}
|
|
10574
|
+
const balanceCount = tokenEntries.length;
|
|
10575
|
+
const lendingCount = lendingProtocols.length;
|
|
10576
|
+
const priceStartIdx = balanceCount + lendingCount;
|
|
10460
10577
|
let nativePriceUsd = 0;
|
|
10578
|
+
const priceByToken = /* @__PURE__ */ new Map();
|
|
10461
10579
|
if (oracleAddr) {
|
|
10462
|
-
|
|
10463
|
-
|
|
10580
|
+
for (let i = 0; i < priceTokens.length; i++) {
|
|
10581
|
+
const priceData = results[priceStartIdx + i] ?? null;
|
|
10582
|
+
const px = Number(decodeU256Word(priceData)) / 1e8;
|
|
10583
|
+
if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
|
|
10584
|
+
}
|
|
10585
|
+
const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
|
|
10586
|
+
nativePriceUsd = Number(decodeU256Word(nativePriceData)) / 1e8;
|
|
10464
10587
|
}
|
|
10465
10588
|
let idx = 0;
|
|
10466
10589
|
const tokens = [];
|
|
@@ -10470,8 +10593,20 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10470
10593
|
const balance = decodeU256Word(results[idx] ?? null);
|
|
10471
10594
|
const balF64 = Number(balance) / 10 ** entry.decimals;
|
|
10472
10595
|
const symbolUpper = entry.symbol.toUpperCase();
|
|
10473
|
-
const
|
|
10474
|
-
|
|
10596
|
+
const tokenAddrLower = entry.address.toLowerCase();
|
|
10597
|
+
let priceUsd;
|
|
10598
|
+
let valueUsd;
|
|
10599
|
+
if (symbolUpper.includes("USD")) {
|
|
10600
|
+
priceUsd = 1;
|
|
10601
|
+
valueUsd = balF64;
|
|
10602
|
+
} else if (tokenAddrLower === wrappedNative.toLowerCase()) {
|
|
10603
|
+
priceUsd = nativePriceUsd;
|
|
10604
|
+
valueUsd = balF64 * nativePriceUsd;
|
|
10605
|
+
} else {
|
|
10606
|
+
const px = priceByToken.get(tokenAddrLower);
|
|
10607
|
+
priceUsd = px ?? 0;
|
|
10608
|
+
valueUsd = px && px > 0 ? balF64 * px : 0;
|
|
10609
|
+
}
|
|
10475
10610
|
totalValueUsd += valueUsd;
|
|
10476
10611
|
tokens.push({
|
|
10477
10612
|
token: entry.address,
|
|
@@ -10512,6 +10647,23 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10512
10647
|
}
|
|
10513
10648
|
idx++;
|
|
10514
10649
|
}
|
|
10650
|
+
try {
|
|
10651
|
+
const client = createPublicClient24({ transport: http24(rpc) });
|
|
10652
|
+
const nativeBalance = await client.getBalance({ address: user });
|
|
10653
|
+
if (nativeBalance > 0n) {
|
|
10654
|
+
const nativeF64 = Number(nativeBalance) / 1e18;
|
|
10655
|
+
const nativeValueUsd = nativeF64 * nativePriceUsd;
|
|
10656
|
+
totalValueUsd += nativeValueUsd;
|
|
10657
|
+
tokens.push({
|
|
10658
|
+
token: wrappedNative,
|
|
10659
|
+
symbol: chain.native_token ?? "NATIVE",
|
|
10660
|
+
balance: nativeBalance,
|
|
10661
|
+
value_usd: nativeValueUsd,
|
|
10662
|
+
price_usd: nativePriceUsd
|
|
10663
|
+
});
|
|
10664
|
+
}
|
|
10665
|
+
} catch {
|
|
10666
|
+
}
|
|
10515
10667
|
return {
|
|
10516
10668
|
timestamp: Date.now(),
|
|
10517
10669
|
chain: chainName,
|
|
@@ -10629,6 +10781,10 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10629
10781
|
const calls = [];
|
|
10630
10782
|
const callLabels = [];
|
|
10631
10783
|
const tokenSymbols = (registry.tokens.get(chainName) ?? []).map((t) => t.symbol);
|
|
10784
|
+
const tokenAddrsByCallIdx = [];
|
|
10785
|
+
const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
|
|
10786
|
+
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
10787
|
+
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
10632
10788
|
for (const symbol of tokenSymbols) {
|
|
10633
10789
|
let entry;
|
|
10634
10790
|
try {
|
|
@@ -10642,6 +10798,7 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10642
10798
|
encodeFunctionData29({ abi: ERC20_ABI5, functionName: "balanceOf", args: [user] })
|
|
10643
10799
|
]);
|
|
10644
10800
|
callLabels.push(`balance:${symbol}`);
|
|
10801
|
+
tokenAddrsByCallIdx.push(entry.address);
|
|
10645
10802
|
}
|
|
10646
10803
|
const lendingProtocols = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending && p.interface === "aave_v3").filter((p) => p.contracts?.["pool"]);
|
|
10647
10804
|
for (const p of lendingProtocols) {
|
|
@@ -10651,10 +10808,16 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10651
10808
|
]);
|
|
10652
10809
|
callLabels.push(`lending:${p.name}`);
|
|
10653
10810
|
}
|
|
10654
|
-
const
|
|
10655
|
-
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
10656
|
-
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
10811
|
+
const priceTokens = [];
|
|
10657
10812
|
if (oracleAddr) {
|
|
10813
|
+
for (const tokenAddr of tokenAddrsByCallIdx) {
|
|
10814
|
+
calls.push([
|
|
10815
|
+
oracleAddr,
|
|
10816
|
+
encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [tokenAddr] })
|
|
10817
|
+
]);
|
|
10818
|
+
callLabels.push(`price:${tokenAddr}`);
|
|
10819
|
+
priceTokens.push(tokenAddr);
|
|
10820
|
+
}
|
|
10658
10821
|
calls.push([
|
|
10659
10822
|
oracleAddr,
|
|
10660
10823
|
encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [wrappedNative] })
|
|
@@ -10679,10 +10842,19 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10679
10842
|
printOutput({ error: `Multicall failed: ${errMsg(e)}` }, mode);
|
|
10680
10843
|
return;
|
|
10681
10844
|
}
|
|
10845
|
+
const balanceCallCount = tokenAddrsByCallIdx.length;
|
|
10846
|
+
const lendingCallCount = lendingProtocols.length;
|
|
10847
|
+
const priceStartIdx = balanceCallCount + lendingCallCount;
|
|
10682
10848
|
let nativePriceUsd = 0;
|
|
10849
|
+
const priceByToken = /* @__PURE__ */ new Map();
|
|
10683
10850
|
if (oracleAddr) {
|
|
10684
|
-
|
|
10685
|
-
|
|
10851
|
+
for (let i = 0; i < priceTokens.length; i++) {
|
|
10852
|
+
const priceData = results[priceStartIdx + i] ?? null;
|
|
10853
|
+
const px = Number(decodeU2562(priceData)) / 1e8;
|
|
10854
|
+
if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
|
|
10855
|
+
}
|
|
10856
|
+
const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
|
|
10857
|
+
nativePriceUsd = Number(decodeU2562(nativePriceData)) / 1e8;
|
|
10686
10858
|
}
|
|
10687
10859
|
let totalValueUsd = 0;
|
|
10688
10860
|
let idx = 0;
|
|
@@ -10701,12 +10873,21 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10701
10873
|
const decimals = entry.decimals;
|
|
10702
10874
|
const balF64 = Number(balance) / 10 ** decimals;
|
|
10703
10875
|
const symbolUpper = symbol.toUpperCase();
|
|
10704
|
-
const
|
|
10705
|
-
|
|
10876
|
+
const tokenAddrLower = entry.address.toLowerCase();
|
|
10877
|
+
let valueUsd;
|
|
10878
|
+
if (symbolUpper.includes("USD")) {
|
|
10879
|
+
valueUsd = balF64;
|
|
10880
|
+
} else if (tokenAddrLower === wrappedNative.toLowerCase()) {
|
|
10881
|
+
valueUsd = balF64 * nativePriceUsd;
|
|
10882
|
+
} else {
|
|
10883
|
+
const px = priceByToken.get(tokenAddrLower);
|
|
10884
|
+
valueUsd = px && px > 0 ? balF64 * px : null;
|
|
10885
|
+
}
|
|
10886
|
+
if (valueUsd !== null) totalValueUsd += valueUsd;
|
|
10706
10887
|
tokenBalances.push({
|
|
10707
10888
|
symbol,
|
|
10708
10889
|
balance: balF64.toFixed(4),
|
|
10709
|
-
value_usd: valueUsd.toFixed(2)
|
|
10890
|
+
value_usd: valueUsd !== null ? valueUsd.toFixed(2) : null
|
|
10710
10891
|
});
|
|
10711
10892
|
}
|
|
10712
10893
|
idx++;
|
|
@@ -10736,11 +10917,23 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10736
10917
|
}
|
|
10737
10918
|
idx++;
|
|
10738
10919
|
}
|
|
10920
|
+
let nativeBalance = 0n;
|
|
10921
|
+
let nativeValueUsd = 0;
|
|
10922
|
+
try {
|
|
10923
|
+
const client = createPublicClient25({ transport: http25(rpc) });
|
|
10924
|
+
nativeBalance = await client.getBalance({ address: user });
|
|
10925
|
+
const nativeF64 = Number(nativeBalance) / 1e18;
|
|
10926
|
+
nativeValueUsd = nativeF64 * nativePriceUsd;
|
|
10927
|
+
if (nativeBalance > 0n) totalValueUsd += nativeValueUsd;
|
|
10928
|
+
} catch {
|
|
10929
|
+
}
|
|
10739
10930
|
printOutput(
|
|
10740
10931
|
{
|
|
10741
10932
|
address: user,
|
|
10742
10933
|
chain: chain.name,
|
|
10743
10934
|
native_price_usd: nativePriceUsd.toFixed(2),
|
|
10935
|
+
native_balance: (Number(nativeBalance) / 1e18).toFixed(6),
|
|
10936
|
+
native_value_usd: nativeValueUsd.toFixed(2),
|
|
10744
10937
|
total_value_usd: totalValueUsd.toFixed(2),
|
|
10745
10938
|
token_balances: tokenBalances,
|
|
10746
10939
|
lending_positions: lendingPositions
|
|
@@ -11006,38 +11199,50 @@ function registerPrice(parent, getOpts) {
|
|
|
11006
11199
|
|
|
11007
11200
|
// src/commands/wallet.ts
|
|
11008
11201
|
init_dist();
|
|
11009
|
-
import { createPublicClient as
|
|
11202
|
+
import { createPublicClient as createPublicClient26, http as http26, formatEther } from "viem";
|
|
11203
|
+
function resolveCurrentAddress(override) {
|
|
11204
|
+
if (override) return { address: override, source: "flag" };
|
|
11205
|
+
try {
|
|
11206
|
+
const { address, signer } = resolveWalletWithSigner();
|
|
11207
|
+
return { address, source: signer ? "ows" : process.env["DEFI_PRIVATE_KEY"] ? "private_key" : "env" };
|
|
11208
|
+
} catch {
|
|
11209
|
+
return { address: null, source: "none" };
|
|
11210
|
+
}
|
|
11211
|
+
}
|
|
11010
11212
|
function registerWallet(parent, getOpts) {
|
|
11011
11213
|
const wallet = parent.command("wallet").description("Wallet management");
|
|
11012
|
-
wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
11214
|
+
wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to OWS vault, DEFI_PRIVATE_KEY, or DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
11013
11215
|
const chainName = requireChain(parent, getOpts);
|
|
11014
11216
|
if (!chainName) return;
|
|
11015
|
-
const addr = opts.address
|
|
11217
|
+
const { address: addr, source } = resolveCurrentAddress(opts.address);
|
|
11016
11218
|
if (!addr) {
|
|
11017
|
-
printOutput({
|
|
11219
|
+
printOutput({
|
|
11220
|
+
error: "No wallet configured. Set DEFI_WALLET_ADDRESS, set DEFI_PRIVATE_KEY, or pass --address."
|
|
11221
|
+
}, getOpts());
|
|
11018
11222
|
return;
|
|
11019
11223
|
}
|
|
11020
11224
|
const registry = Registry.loadEmbedded();
|
|
11021
11225
|
const chain = registry.getChain(chainName);
|
|
11022
|
-
const client =
|
|
11226
|
+
const client = createPublicClient26({ transport: http26(chain.effectiveRpcUrl()) });
|
|
11023
11227
|
const balance = await client.getBalance({ address: addr });
|
|
11024
11228
|
printOutput({
|
|
11025
11229
|
chain: chain.name,
|
|
11026
11230
|
address: addr,
|
|
11231
|
+
wallet_source: source,
|
|
11027
11232
|
native_token: chain.native_token,
|
|
11028
11233
|
balance_wei: balance,
|
|
11029
11234
|
balance_formatted: formatEther(balance)
|
|
11030
11235
|
}, getOpts());
|
|
11031
11236
|
});
|
|
11032
11237
|
wallet.command("address").description("Show configured wallet address").action(async () => {
|
|
11033
|
-
const
|
|
11034
|
-
printOutput({ address
|
|
11238
|
+
const { address, source } = resolveCurrentAddress();
|
|
11239
|
+
printOutput({ address, source }, getOpts());
|
|
11035
11240
|
});
|
|
11036
11241
|
}
|
|
11037
11242
|
|
|
11038
11243
|
// src/commands/token.ts
|
|
11039
11244
|
init_dist();
|
|
11040
|
-
import { createPublicClient as
|
|
11245
|
+
import { createPublicClient as createPublicClient27, http as http27, maxUint256 } from "viem";
|
|
11041
11246
|
function registerToken(parent, getOpts, makeExecutor2) {
|
|
11042
11247
|
const token = parent.command("token").description("Token operations: approve, allowance, transfer, balance");
|
|
11043
11248
|
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) => {
|
|
@@ -11050,7 +11255,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
11050
11255
|
}
|
|
11051
11256
|
const registry = Registry.loadEmbedded();
|
|
11052
11257
|
const chain = registry.getChain(chainName);
|
|
11053
|
-
const client =
|
|
11258
|
+
const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
|
|
11054
11259
|
const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
|
|
11055
11260
|
const [balance, symbol, decimals] = await Promise.all([
|
|
11056
11261
|
client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "balanceOf", args: [owner] }),
|
|
@@ -11086,7 +11291,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
11086
11291
|
}
|
|
11087
11292
|
const registry = Registry.loadEmbedded();
|
|
11088
11293
|
const chain = registry.getChain(chainName);
|
|
11089
|
-
const client =
|
|
11294
|
+
const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
|
|
11090
11295
|
const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
|
|
11091
11296
|
const allowance = await client.readContract({
|
|
11092
11297
|
address: tokenAddr,
|
|
@@ -11204,6 +11409,17 @@ var CCTP_USDC_ADDRESSES = {
|
|
|
11204
11409
|
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
11205
11410
|
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
|
|
11206
11411
|
};
|
|
11412
|
+
function cctpMinFeeGuard(amountWei, maxFeeSubunits, feeUsdc) {
|
|
11413
|
+
if (amountWei <= maxFeeSubunits) {
|
|
11414
|
+
const amountUsdc = Number(amountWei) / 1e6;
|
|
11415
|
+
return {
|
|
11416
|
+
error: `CCTP: amount ${amountWei.toString()} (${amountUsdc} USDC) is below the minimum bridge fee of ${maxFeeSubunits} (${feeUsdc} USDC). Increase --amount.`,
|
|
11417
|
+
minimum_amount_wei: maxFeeSubunits.toString(),
|
|
11418
|
+
minimum_amount_usdc: feeUsdc
|
|
11419
|
+
};
|
|
11420
|
+
}
|
|
11421
|
+
return null;
|
|
11422
|
+
}
|
|
11207
11423
|
async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
|
|
11208
11424
|
try {
|
|
11209
11425
|
const res = await fetch(`${CCTP_FEE_API}/${srcDomain}/${dstDomain}`);
|
|
@@ -11292,12 +11508,9 @@ function registerBridge(parent, getOpts) {
|
|
|
11292
11508
|
}
|
|
11293
11509
|
const amountUsdc = Number(BigInt(opts.amount)) / 1e6;
|
|
11294
11510
|
const { fee, maxFeeSubunits } = await getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc);
|
|
11295
|
-
|
|
11296
|
-
|
|
11297
|
-
|
|
11298
|
-
minimum_amount_wei: maxFeeSubunits.toString(),
|
|
11299
|
-
minimum_amount_usdc: fee
|
|
11300
|
-
}, getOpts());
|
|
11511
|
+
const guardErr = cctpMinFeeGuard(BigInt(opts.amount), maxFeeSubunits, fee);
|
|
11512
|
+
if (guardErr) {
|
|
11513
|
+
printOutput(guardErr, getOpts());
|
|
11301
11514
|
return;
|
|
11302
11515
|
}
|
|
11303
11516
|
const recipientPadded = `0x${"0".repeat(24)}${recipient.replace("0x", "").toLowerCase()}`;
|
|
@@ -11578,12 +11791,14 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
11578
11791
|
slippagePct,
|
|
11579
11792
|
wallet
|
|
11580
11793
|
);
|
|
11794
|
+
const fromLower = fromAddr.toLowerCase();
|
|
11795
|
+
const isNativeInput = fromLower === "0x0000000000000000000000000000000000000000" || fromLower === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
11581
11796
|
const tx = {
|
|
11582
11797
|
description: `OpenOcean: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
|
|
11583
11798
|
to: swap.to,
|
|
11584
11799
|
data: swap.data,
|
|
11585
11800
|
value: parseBigIntValue(swap.value),
|
|
11586
|
-
approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }]
|
|
11801
|
+
...isNativeInput ? {} : { approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }] }
|
|
11587
11802
|
};
|
|
11588
11803
|
const result = await executor.execute(tx);
|
|
11589
11804
|
printOutput({
|
|
@@ -11675,7 +11890,8 @@ import pc2 from "picocolors";
|
|
|
11675
11890
|
import { createInterface } from "readline";
|
|
11676
11891
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
11677
11892
|
import { resolve as resolve4 } from "path";
|
|
11678
|
-
|
|
11893
|
+
import { homedir as homedir2 } from "os";
|
|
11894
|
+
var DEFI_DIR = resolve4(homedir2(), ".defi");
|
|
11679
11895
|
var ENV_FILE = resolve4(DEFI_DIR, ".env");
|
|
11680
11896
|
function ensureDefiDir() {
|
|
11681
11897
|
if (!existsSync3(DEFI_DIR)) mkdirSync2(DEFI_DIR, { recursive: true, mode: 448 });
|
|
@@ -11710,6 +11926,44 @@ function writeEnvFile(env) {
|
|
|
11710
11926
|
function ask(rl, question) {
|
|
11711
11927
|
return new Promise((res) => rl.question(question, (answer) => res(answer.trim())));
|
|
11712
11928
|
}
|
|
11929
|
+
function askSecret(rl, question) {
|
|
11930
|
+
const stdin = process.stdin;
|
|
11931
|
+
if (!stdin.isTTY) return ask(rl, question);
|
|
11932
|
+
process.stdout.write(question);
|
|
11933
|
+
const rlAny = rl;
|
|
11934
|
+
const original = rlAny._writeToOutput;
|
|
11935
|
+
rlAny._writeToOutput = (s) => {
|
|
11936
|
+
if (s.includes("\n") || s.includes("\r")) {
|
|
11937
|
+
original?.call(rl, s);
|
|
11938
|
+
}
|
|
11939
|
+
};
|
|
11940
|
+
return new Promise((res) => {
|
|
11941
|
+
rl.question("", (answer) => {
|
|
11942
|
+
rlAny._writeToOutput = original;
|
|
11943
|
+
process.stdout.write("\n");
|
|
11944
|
+
res(answer.trim());
|
|
11945
|
+
});
|
|
11946
|
+
});
|
|
11947
|
+
}
|
|
11948
|
+
function maskRpcUrl(s) {
|
|
11949
|
+
try {
|
|
11950
|
+
const u = new URL(s);
|
|
11951
|
+
if (u.pathname && u.pathname !== "/" && u.pathname.length > 1) {
|
|
11952
|
+
return `${u.protocol}//${u.host}/***`;
|
|
11953
|
+
}
|
|
11954
|
+
return `${u.protocol}//${u.host}`;
|
|
11955
|
+
} catch {
|
|
11956
|
+
return "***";
|
|
11957
|
+
}
|
|
11958
|
+
}
|
|
11959
|
+
function isValidRpcUrl(s) {
|
|
11960
|
+
try {
|
|
11961
|
+
const u = new URL(s);
|
|
11962
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
11963
|
+
} catch {
|
|
11964
|
+
return false;
|
|
11965
|
+
}
|
|
11966
|
+
}
|
|
11713
11967
|
function isValidAddress(s) {
|
|
11714
11968
|
return /^0x[0-9a-fA-F]{40}$/.test(s);
|
|
11715
11969
|
}
|
|
@@ -11734,7 +11988,14 @@ function registerSetup(program2) {
|
|
|
11734
11988
|
if (Object.keys(existing).length > 0) {
|
|
11735
11989
|
console.log(pc2.white(" Current configuration:"));
|
|
11736
11990
|
for (const [key, value] of Object.entries(existing)) {
|
|
11737
|
-
|
|
11991
|
+
let masked;
|
|
11992
|
+
if (key.toLowerCase().includes("key")) {
|
|
11993
|
+
masked = value.slice(0, 6) + "..." + value.slice(-4);
|
|
11994
|
+
} else if (key.endsWith("RPC_URL")) {
|
|
11995
|
+
masked = maskRpcUrl(value);
|
|
11996
|
+
} else {
|
|
11997
|
+
masked = value;
|
|
11998
|
+
}
|
|
11738
11999
|
console.log(` ${pc2.cyan(key.padEnd(24))} ${pc2.gray(masked)}`);
|
|
11739
12000
|
}
|
|
11740
12001
|
console.log();
|
|
@@ -11748,7 +12009,7 @@ function registerSetup(program2) {
|
|
|
11748
12009
|
}
|
|
11749
12010
|
const newEnv = {};
|
|
11750
12011
|
console.log(pc2.cyan(pc2.bold(" Wallet")));
|
|
11751
|
-
const privateKey = await
|
|
12012
|
+
const privateKey = await askSecret(rl, " Private key (optional, for --broadcast, 0x... \u2014 input hidden): ");
|
|
11752
12013
|
if (privateKey) {
|
|
11753
12014
|
const normalized = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
|
|
11754
12015
|
if (!isValidPrivateKey(normalized)) {
|
|
@@ -11784,6 +12045,10 @@ function registerSetup(program2) {
|
|
|
11784
12045
|
for (const { env, label } of rpcPrompts) {
|
|
11785
12046
|
const value = await ask(rl, ` ${label} RPC URL: `);
|
|
11786
12047
|
if (value) {
|
|
12048
|
+
if (!isValidRpcUrl(value)) {
|
|
12049
|
+
console.log(pc2.yellow(` Invalid URL (${value}). Skipped \u2014 re-run setup to retry.`));
|
|
12050
|
+
continue;
|
|
12051
|
+
}
|
|
11787
12052
|
newEnv[env] = value;
|
|
11788
12053
|
console.log(` ${pc2.green("OK")} ${label} RPC set`);
|
|
11789
12054
|
}
|
|
@@ -11807,7 +12072,7 @@ function registerSetup(program2) {
|
|
|
11807
12072
|
];
|
|
11808
12073
|
for (const [k, label] of rpcSummary) {
|
|
11809
12074
|
const v = finalEnv[k];
|
|
11810
|
-
if (v) console.log(` ${label}: ${pc2.gray(v)}`);
|
|
12075
|
+
if (v) console.log(` ${label}: ${pc2.gray(maskRpcUrl(v))}`);
|
|
11811
12076
|
}
|
|
11812
12077
|
console.log(pc2.bold(pc2.white("\n Next steps:")));
|
|
11813
12078
|
console.log(` ${pc2.green("defi portfolio")} view balances & positions`);
|
|
@@ -11826,11 +12091,11 @@ function registerSetup(program2) {
|
|
|
11826
12091
|
// src/commands/ows.ts
|
|
11827
12092
|
import pc3 from "picocolors";
|
|
11828
12093
|
init_dist();
|
|
11829
|
-
import { createPublicClient as
|
|
12094
|
+
import { createPublicClient as createPublicClient28, http as http28, formatEther as formatEther2 } from "viem";
|
|
11830
12095
|
async function getEvmBalance(address, chainName) {
|
|
11831
12096
|
const registry = Registry.loadEmbedded();
|
|
11832
12097
|
const chain = registry.getChain(chainName);
|
|
11833
|
-
const client =
|
|
12098
|
+
const client = createPublicClient28({ transport: http28(chain.effectiveRpcUrl()) });
|
|
11834
12099
|
const balance = await client.getBalance({ address });
|
|
11835
12100
|
return {
|
|
11836
12101
|
native_token: chain.native_token,
|