@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/main.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) {
|
|
@@ -7337,11 +7341,29 @@ var Executor = class _Executor {
|
|
|
7337
7341
|
static applyGasBuffer(gas) {
|
|
7338
7342
|
return gas * GAS_BUFFER_BPS / 10000n;
|
|
7339
7343
|
}
|
|
7344
|
+
/**
|
|
7345
|
+
* EIP-1559 max-fee formula: `baseFee * 1.25 + priorityFee`.
|
|
7346
|
+
* Extracted as a static so the math is unit-testable without mocking viem.
|
|
7347
|
+
*/
|
|
7348
|
+
static computeMaxFee(baseFeePerGas, priorityFee) {
|
|
7349
|
+
return baseFeePerGas * 125n / 100n + priorityFee;
|
|
7350
|
+
}
|
|
7340
7351
|
/**
|
|
7341
7352
|
* Check allowance for a single token/spender pair and send an approve tx if needed.
|
|
7342
7353
|
* Only called in broadcast mode (not dry-run).
|
|
7343
7354
|
*/
|
|
7355
|
+
/**
|
|
7356
|
+
* Recognize the standard native-token sentinels:
|
|
7357
|
+
* - 0x0000…0000 — defi-cli's internal marker for native gas tokens
|
|
7358
|
+
* - 0xeeee…eeee — 1inch / KyberSwap / OpenOcean canonical sentinel
|
|
7359
|
+
* Any address normalised to lowercase matches case-insensitively.
|
|
7360
|
+
*/
|
|
7361
|
+
static isNativeSentinel(token) {
|
|
7362
|
+
const t = token.toLowerCase();
|
|
7363
|
+
return t === "0x0000000000000000000000000000000000000000" || t === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
7364
|
+
}
|
|
7344
7365
|
async checkAndApprove(token, spender, amount, owner, publicClient, walletClient) {
|
|
7366
|
+
if (_Executor.isNativeSentinel(token)) return;
|
|
7345
7367
|
const allowance = await publicClient.readContract({
|
|
7346
7368
|
address: token,
|
|
7347
7369
|
abi: ERC20_ABI,
|
|
@@ -7422,9 +7444,12 @@ var Executor = class _Executor {
|
|
|
7422
7444
|
/**
|
|
7423
7445
|
* Fetch EIP-1559 fee params. Returns [maxFeePerGas, maxPriorityFeePerGas].
|
|
7424
7446
|
*
|
|
7425
|
-
* Strategy: read the latest block's `baseFeePerGas` and
|
|
7426
|
-
*
|
|
7427
|
-
*
|
|
7447
|
+
* Strategy: read the latest block's `baseFeePerGas` and apply the conservative
|
|
7448
|
+
* formula `maxFee = baseFee * 1.25 + priorityFee` (one block of head-room — the
|
|
7449
|
+
* 12.5% per-block max bump). The canonical 2× wastes budget on chains where
|
|
7450
|
+
* baseFee is already elevated (e.g., Mantle ~50 gwei → 100 gwei doubled the
|
|
7451
|
+
* MNT requirement and broke multi-step flows). Falls back to
|
|
7452
|
+
* `getGasPrice() + priorityFee` only when
|
|
7428
7453
|
* the chain doesn't expose `baseFeePerGas` (pre-1559).
|
|
7429
7454
|
*
|
|
7430
7455
|
* Why not gasPrice * 2: `getGasPrice()` returns `baseFee + priorityFee`, so
|
|
@@ -7443,8 +7468,7 @@ var Executor = class _Executor {
|
|
|
7443
7468
|
try {
|
|
7444
7469
|
const block = await client.getBlock({ blockTag: "latest" });
|
|
7445
7470
|
if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
|
|
7446
|
-
|
|
7447
|
-
return [maxFee, priorityFee];
|
|
7471
|
+
return [_Executor.computeMaxFee(block.baseFeePerGas, priorityFee), priorityFee];
|
|
7448
7472
|
}
|
|
7449
7473
|
} catch {
|
|
7450
7474
|
}
|
|
@@ -7487,6 +7511,7 @@ var Executor = class _Executor {
|
|
|
7487
7511
|
if (tx.approvals && tx.approvals.length > 0) {
|
|
7488
7512
|
const pendingApprovals = [];
|
|
7489
7513
|
for (const approval of tx.approvals) {
|
|
7514
|
+
if (_Executor.isNativeSentinel(approval.token)) continue;
|
|
7490
7515
|
try {
|
|
7491
7516
|
const allowance = await client.readContract({
|
|
7492
7517
|
address: approval.token,
|
|
@@ -8265,7 +8290,8 @@ function handleSchema(params) {
|
|
|
8265
8290
|
function registerSchema(parent, getOpts) {
|
|
8266
8291
|
parent.command("schema [command]").description("Output JSON schema for a command (agent-friendly)").option("--all", "Show all schemas").action(async (command, opts) => {
|
|
8267
8292
|
const mode = getOpts();
|
|
8268
|
-
const
|
|
8293
|
+
const raw = opts.all ? "all" : command ?? "all";
|
|
8294
|
+
const action = raw.replace(/-/g, ".");
|
|
8269
8295
|
const params = { action };
|
|
8270
8296
|
const schema = handleSchema(params);
|
|
8271
8297
|
printOutput(schema, mode);
|
|
@@ -8456,6 +8482,11 @@ function buildPipelineSteps(p, input = {}) {
|
|
|
8456
8482
|
["--protocol", slug, "slug"],
|
|
8457
8483
|
["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"]
|
|
8458
8484
|
]);
|
|
8485
|
+
const claimAutoStakeNftGauge = () => `defi ${chainFlag}lp claim ` + buildCmd([
|
|
8486
|
+
["--protocol", slug, "slug"],
|
|
8487
|
+
["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"],
|
|
8488
|
+
["--token-id", input.tokenId, "token-id-from-mint-result"]
|
|
8489
|
+
]);
|
|
8459
8490
|
switch (p.reward_strategy) {
|
|
8460
8491
|
case "lp_fee_only":
|
|
8461
8492
|
return [
|
|
@@ -8480,11 +8511,18 @@ function buildPipelineSteps(p, input = {}) {
|
|
|
8480
8511
|
{ step: "stake", function: "gauge.deposit(amount)", cli_command: baseFarm },
|
|
8481
8512
|
{ step: "claim", function: "gauge.earned(token, account) \u2192 gauge.getReward(account, tokens[])", cli_command: claimWithGauge() }
|
|
8482
8513
|
];
|
|
8483
|
-
case "auto_stake":
|
|
8514
|
+
case "auto_stake": {
|
|
8515
|
+
const isNftAutoStake = p.interface === "uniswap_v3";
|
|
8484
8516
|
return [
|
|
8485
8517
|
{ step: "mint", function: "Router.addLiquidity / NPM.mint", note: "LP automatically receives x(3,3) emissions \u2014 no separate stake step", cli_command: baseAdd },
|
|
8486
|
-
{
|
|
8518
|
+
{
|
|
8519
|
+
step: "claim",
|
|
8520
|
+
function: isNftAutoStake ? "NPM.getPeriodReward(currentEpoch, tokenId, tokens[], receiver)" : "gauge.getReward(account, tokens[])",
|
|
8521
|
+
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)",
|
|
8522
|
+
cli_command: isNftAutoStake ? claimAutoStakeNftGauge() : claimWithGauge()
|
|
8523
|
+
}
|
|
8487
8524
|
];
|
|
8525
|
+
}
|
|
8488
8526
|
case "on_chain_masterchef":
|
|
8489
8527
|
return [
|
|
8490
8528
|
{ step: "mint", function: "NPM.mint or pool.mint", cli_command: baseAdd },
|
|
@@ -9195,7 +9233,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9195
9233
|
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."
|
|
9196
9234
|
}, getOpts());
|
|
9197
9235
|
});
|
|
9198
|
-
lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").
|
|
9236
|
+
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) => {
|
|
9199
9237
|
const executor = makeExecutor2();
|
|
9200
9238
|
const chainName = parent.opts().chain;
|
|
9201
9239
|
if (!chainName) {
|
|
@@ -9211,6 +9249,9 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9211
9249
|
if (iface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
9212
9250
|
if (!opts.pool) throw new Error(`--pool is required for ${protocol.name} (Liquidity Book \u2014 pass --pool <addr>)`);
|
|
9213
9251
|
if (!opts.bins) throw new Error("--bins <id1,id2,...> is required for Merchant Moe LB remove");
|
|
9252
|
+
if (!opts.tokenA || !opts.tokenB) {
|
|
9253
|
+
throw new Error(`--token-a and --token-b are required for ${protocol.name} (Liquidity Book) remove`);
|
|
9254
|
+
}
|
|
9214
9255
|
const lbAdapter = createMerchantMoeLB(protocol, rpcUrl);
|
|
9215
9256
|
const tokenA2 = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
|
|
9216
9257
|
const tokenB2 = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
|
|
@@ -9253,8 +9294,22 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9253
9294
|
printOutput({ step: "lb_remove", ...result }, getOpts());
|
|
9254
9295
|
return;
|
|
9255
9296
|
}
|
|
9256
|
-
const
|
|
9257
|
-
const
|
|
9297
|
+
const NFT_REMOVE_IFACES = /* @__PURE__ */ new Set(["uniswap_v3", "algebra_v3", "thena_cl", "hybra"]);
|
|
9298
|
+
const isNftRemove = !!opts.tokenId && NFT_REMOVE_IFACES.has(iface);
|
|
9299
|
+
if (!isNftRemove) {
|
|
9300
|
+
const missing = [];
|
|
9301
|
+
if (!opts.tokenA) missing.push("--token-a");
|
|
9302
|
+
if (!opts.tokenB) missing.push("--token-b");
|
|
9303
|
+
if (!opts.liquidity) missing.push("--liquidity");
|
|
9304
|
+
if (missing.length > 0) {
|
|
9305
|
+
printOutput({
|
|
9306
|
+
error: `${missing.join(", ")} required for ${protocol.name} remove (or pass --token-id for V3/CL NFT-based remove).`
|
|
9307
|
+
}, getOpts());
|
|
9308
|
+
return;
|
|
9309
|
+
}
|
|
9310
|
+
}
|
|
9311
|
+
const tokenA = opts.tokenA ? opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address : void 0;
|
|
9312
|
+
const tokenB = opts.tokenB ? opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address : void 0;
|
|
9258
9313
|
const poolAddr = opts.pool ? opts.pool : void 0;
|
|
9259
9314
|
let didUnstake = false;
|
|
9260
9315
|
if (iface === "algebra_v3" && protocol.contracts?.["farming_center"] && opts.tokenId && poolAddr) {
|
|
@@ -9304,7 +9359,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9304
9359
|
if (iface === "hybra" && (!wOpts || wOpts.redeemType === 1)) {
|
|
9305
9360
|
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");
|
|
9306
9361
|
}
|
|
9307
|
-
const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, BigInt(opts.liquidity), tokenId, wOpts);
|
|
9362
|
+
const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, opts.liquidity ? BigInt(opts.liquidity) : 0n, tokenId, wOpts);
|
|
9308
9363
|
const withdrawResult = await executor.execute(withdrawTx);
|
|
9309
9364
|
printOutput({ step: "unstake_gauge", ...withdrawResult }, getOpts());
|
|
9310
9365
|
if (withdrawResult.status !== "confirmed" && withdrawResult.status !== "simulated") {
|
|
@@ -9319,11 +9374,31 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9319
9374
|
}
|
|
9320
9375
|
process.stderr.write("Step 2/2: Removing liquidity...\n");
|
|
9321
9376
|
const dexAdapter = createDex(protocol, rpcUrl);
|
|
9377
|
+
let removeLiquidity = opts.liquidity ? BigInt(opts.liquidity) : 0n;
|
|
9378
|
+
if (isNftRemove && removeLiquidity === 0n) {
|
|
9379
|
+
const npm = protocol.contracts?.["position_manager"];
|
|
9380
|
+
if (npm) {
|
|
9381
|
+
const c = createPublicClient23({ transport: http23(rpcUrl) });
|
|
9382
|
+
const pos = await detectV3Liquidity(c, npm, BigInt(opts.tokenId));
|
|
9383
|
+
if (pos) {
|
|
9384
|
+
removeLiquidity = pos.liquidity;
|
|
9385
|
+
process.stderr.write(` Read live liquidity ${removeLiquidity} from NPM.positions(${opts.tokenId}).
|
|
9386
|
+
`);
|
|
9387
|
+
}
|
|
9388
|
+
}
|
|
9389
|
+
}
|
|
9390
|
+
if (isNftRemove && removeLiquidity === 0n) {
|
|
9391
|
+
printOutput({
|
|
9392
|
+
error: `tokenId ${opts.tokenId} has zero liquidity (already removed?). Pass --liquidity explicitly to override, or pick a different tokenId.`
|
|
9393
|
+
}, getOpts());
|
|
9394
|
+
return;
|
|
9395
|
+
}
|
|
9396
|
+
const ZERO = "0x0000000000000000000000000000000000000000";
|
|
9322
9397
|
const removeTx = await dexAdapter.buildRemoveLiquidity({
|
|
9323
9398
|
protocol: protocol.name,
|
|
9324
|
-
token_a: tokenA,
|
|
9325
|
-
token_b: tokenB,
|
|
9326
|
-
liquidity:
|
|
9399
|
+
token_a: tokenA ?? ZERO,
|
|
9400
|
+
token_b: tokenB ?? ZERO,
|
|
9401
|
+
liquidity: removeLiquidity,
|
|
9327
9402
|
recipient,
|
|
9328
9403
|
token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0
|
|
9329
9404
|
});
|
|
@@ -9702,12 +9777,20 @@ function resolveTokenAddress(registry, chainName, tokenOrAddress) {
|
|
|
9702
9777
|
return registry.resolveToken(chainName, tokenOrAddress).address;
|
|
9703
9778
|
}
|
|
9704
9779
|
var FALLBACK_ADDRESS = "0x0000000000000000000000000000000000000001";
|
|
9780
|
+
var warnedFallback = false;
|
|
9705
9781
|
function resolveWallet(override) {
|
|
9706
9782
|
if (override) return override;
|
|
9707
9783
|
try {
|
|
9708
9784
|
const { address } = resolveWalletWithSigner();
|
|
9709
9785
|
return address;
|
|
9710
9786
|
} catch {
|
|
9787
|
+
if (!warnedFallback) {
|
|
9788
|
+
process.stderr.write(
|
|
9789
|
+
`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.
|
|
9790
|
+
`
|
|
9791
|
+
);
|
|
9792
|
+
warnedFallback = true;
|
|
9793
|
+
}
|
|
9711
9794
|
return FALLBACK_ADDRESS;
|
|
9712
9795
|
}
|
|
9713
9796
|
}
|
|
@@ -9810,7 +9893,8 @@ function resolveAsset(registry, chain, asset) {
|
|
|
9810
9893
|
}
|
|
9811
9894
|
async function collectLendingRates(registry, chainName, rpc, assetAddr) {
|
|
9812
9895
|
const protos = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending);
|
|
9813
|
-
const
|
|
9896
|
+
const rates = [];
|
|
9897
|
+
const errors = [];
|
|
9814
9898
|
let first = true;
|
|
9815
9899
|
for (const proto of protos) {
|
|
9816
9900
|
if (!first) {
|
|
@@ -9819,19 +9903,22 @@ async function collectLendingRates(registry, chainName, rpc, assetAddr) {
|
|
|
9819
9903
|
first = false;
|
|
9820
9904
|
try {
|
|
9821
9905
|
const lending = createLending(proto, rpc);
|
|
9822
|
-
const
|
|
9823
|
-
|
|
9906
|
+
const r = await lending.getRates(assetAddr);
|
|
9907
|
+
rates.push(r);
|
|
9824
9908
|
} catch (err) {
|
|
9825
9909
|
process.stderr.write(`Warning: ${proto.name} rates unavailable: ${err}
|
|
9826
9910
|
`);
|
|
9911
|
+
errors.push({ protocol: proto.name, type: "lending_supply", reason: errMsg(err) });
|
|
9827
9912
|
}
|
|
9828
9913
|
}
|
|
9829
|
-
return
|
|
9914
|
+
return { rates, errors };
|
|
9830
9915
|
}
|
|
9831
9916
|
async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
9832
9917
|
const opportunities = [];
|
|
9833
|
-
const
|
|
9834
|
-
|
|
9918
|
+
const errors = [];
|
|
9919
|
+
const lendingResult = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
9920
|
+
errors.push(...lendingResult.errors);
|
|
9921
|
+
for (const r of lendingResult.rates) {
|
|
9835
9922
|
if (r.supply_apy > 0) {
|
|
9836
9923
|
opportunities.push({
|
|
9837
9924
|
protocol: r.protocol,
|
|
@@ -9857,7 +9944,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
|
9857
9944
|
utilization: rates.utilization
|
|
9858
9945
|
});
|
|
9859
9946
|
}
|
|
9860
|
-
} catch {
|
|
9947
|
+
} catch (e) {
|
|
9948
|
+
errors.push({ protocol: proto.name, type: "morpho_vault", reason: errMsg(e) });
|
|
9861
9949
|
}
|
|
9862
9950
|
}
|
|
9863
9951
|
}
|
|
@@ -9873,7 +9961,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
|
9873
9961
|
apy: info.apy ?? 0,
|
|
9874
9962
|
total_assets: info.total_assets.toString()
|
|
9875
9963
|
});
|
|
9876
|
-
} catch {
|
|
9964
|
+
} catch (e) {
|
|
9965
|
+
errors.push({ protocol: proto.name, type: "vault", reason: errMsg(e) });
|
|
9877
9966
|
}
|
|
9878
9967
|
}
|
|
9879
9968
|
}
|
|
@@ -9882,7 +9971,7 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
|
9882
9971
|
const ba = b["apy"] ?? 0;
|
|
9883
9972
|
return ba - aa;
|
|
9884
9973
|
});
|
|
9885
|
-
return opportunities;
|
|
9974
|
+
return { opportunities, errors };
|
|
9886
9975
|
}
|
|
9887
9976
|
async function runYieldScan(registry, asset, output) {
|
|
9888
9977
|
const t0 = Date.now();
|
|
@@ -10024,10 +10113,13 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10024
10113
|
const chain = registry.getChain(chainName);
|
|
10025
10114
|
const rpc = chain.effectiveRpcUrl();
|
|
10026
10115
|
const assetAddr = resolveAsset(registry, chainName, opts.asset);
|
|
10027
|
-
const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10116
|
+
const { rates: results, errors: ratesErrors } = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10028
10117
|
if (results.length === 0) {
|
|
10029
10118
|
printOutput(
|
|
10030
|
-
|
|
10119
|
+
ratesErrors.length > 0 ? {
|
|
10120
|
+
error: `Could not collect lending rates for '${opts.asset}': ${ratesErrors.length} probe(s) failed (likely RPC throttling). Retry, or set ${chainName.toUpperCase()}_RPC_URL.`,
|
|
10121
|
+
failed_probes: ratesErrors
|
|
10122
|
+
} : { error: `No lending rate data available for asset '${opts.asset}'` },
|
|
10031
10123
|
getOpts()
|
|
10032
10124
|
);
|
|
10033
10125
|
process.exit(1);
|
|
@@ -10269,9 +10361,16 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10269
10361
|
const assetAddr = resolveAsset(registry, chainName, asset);
|
|
10270
10362
|
const strategy = opts.strategy ?? "auto";
|
|
10271
10363
|
if (strategy === "auto") {
|
|
10272
|
-
const opportunities = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
|
|
10364
|
+
const { opportunities, errors } = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
|
|
10273
10365
|
if (opportunities.length === 0) {
|
|
10274
|
-
|
|
10366
|
+
if (errors.length > 0) {
|
|
10367
|
+
printOutput({
|
|
10368
|
+
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.`,
|
|
10369
|
+
failed_probes: errors
|
|
10370
|
+
}, getOpts());
|
|
10371
|
+
} else {
|
|
10372
|
+
printOutput({ error: `No yield opportunities found for '${asset}'` }, getOpts());
|
|
10373
|
+
}
|
|
10275
10374
|
process.exit(1);
|
|
10276
10375
|
return;
|
|
10277
10376
|
}
|
|
@@ -10301,9 +10400,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10301
10400
|
getOpts()
|
|
10302
10401
|
);
|
|
10303
10402
|
} else if (strategy === "best-supply") {
|
|
10304
|
-
const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10403
|
+
const { rates: results, errors: rErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10305
10404
|
if (results.length === 0) {
|
|
10306
|
-
printOutput(
|
|
10405
|
+
printOutput(
|
|
10406
|
+
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}'` },
|
|
10407
|
+
getOpts()
|
|
10408
|
+
);
|
|
10307
10409
|
process.exit(1);
|
|
10308
10410
|
return;
|
|
10309
10411
|
}
|
|
@@ -10326,9 +10428,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10326
10428
|
getOpts()
|
|
10327
10429
|
);
|
|
10328
10430
|
} else if (strategy === "leverage-loop") {
|
|
10329
|
-
const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10431
|
+
const { rates: results, errors: lErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10330
10432
|
if (results.length === 0) {
|
|
10331
|
-
printOutput(
|
|
10433
|
+
printOutput(
|
|
10434
|
+
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}'` },
|
|
10435
|
+
getOpts()
|
|
10436
|
+
);
|
|
10332
10437
|
process.exit(1);
|
|
10333
10438
|
return;
|
|
10334
10439
|
}
|
|
@@ -10392,14 +10497,14 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10392
10497
|
|
|
10393
10498
|
// src/commands/portfolio.ts
|
|
10394
10499
|
init_dist();
|
|
10395
|
-
import { encodeFunctionData as encodeFunctionData29, parseAbi as parseAbi33 } from "viem";
|
|
10500
|
+
import { createPublicClient as createPublicClient25, encodeFunctionData as encodeFunctionData29, http as http25, parseAbi as parseAbi33 } from "viem";
|
|
10396
10501
|
|
|
10397
10502
|
// src/portfolio-tracker.ts
|
|
10398
10503
|
init_dist();
|
|
10399
10504
|
import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
|
|
10400
10505
|
import { homedir } from "os";
|
|
10401
10506
|
import { resolve as resolve3 } from "path";
|
|
10402
|
-
import { encodeFunctionData as encodeFunctionData28, parseAbi as parseAbi31 } from "viem";
|
|
10507
|
+
import { createPublicClient as createPublicClient24, encodeFunctionData as encodeFunctionData28, http as http24, parseAbi as parseAbi31 } from "viem";
|
|
10403
10508
|
var ERC20_ABI4 = parseAbi31([
|
|
10404
10509
|
"function balanceOf(address owner) external view returns (uint256)"
|
|
10405
10510
|
]);
|
|
@@ -10450,7 +10555,16 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10450
10555
|
const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
|
|
10451
10556
|
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
10452
10557
|
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
10558
|
+
const priceTokens = [];
|
|
10453
10559
|
if (oracleAddr) {
|
|
10560
|
+
for (const t of tokenEntries) {
|
|
10561
|
+
calls.push([
|
|
10562
|
+
oracleAddr,
|
|
10563
|
+
encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [t.address] })
|
|
10564
|
+
]);
|
|
10565
|
+
callLabels.push(`price:${t.address}`);
|
|
10566
|
+
priceTokens.push(t.address);
|
|
10567
|
+
}
|
|
10454
10568
|
calls.push([
|
|
10455
10569
|
oracleAddr,
|
|
10456
10570
|
encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [wrappedNative] })
|
|
@@ -10461,10 +10575,19 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10461
10575
|
if (calls.length > 0) {
|
|
10462
10576
|
results = await multicallRead(rpc, calls);
|
|
10463
10577
|
}
|
|
10578
|
+
const balanceCount = tokenEntries.length;
|
|
10579
|
+
const lendingCount = lendingProtocols.length;
|
|
10580
|
+
const priceStartIdx = balanceCount + lendingCount;
|
|
10464
10581
|
let nativePriceUsd = 0;
|
|
10582
|
+
const priceByToken = /* @__PURE__ */ new Map();
|
|
10465
10583
|
if (oracleAddr) {
|
|
10466
|
-
|
|
10467
|
-
|
|
10584
|
+
for (let i = 0; i < priceTokens.length; i++) {
|
|
10585
|
+
const priceData = results[priceStartIdx + i] ?? null;
|
|
10586
|
+
const px = Number(decodeU256Word(priceData)) / 1e8;
|
|
10587
|
+
if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
|
|
10588
|
+
}
|
|
10589
|
+
const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
|
|
10590
|
+
nativePriceUsd = Number(decodeU256Word(nativePriceData)) / 1e8;
|
|
10468
10591
|
}
|
|
10469
10592
|
let idx = 0;
|
|
10470
10593
|
const tokens = [];
|
|
@@ -10474,8 +10597,20 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10474
10597
|
const balance = decodeU256Word(results[idx] ?? null);
|
|
10475
10598
|
const balF64 = Number(balance) / 10 ** entry.decimals;
|
|
10476
10599
|
const symbolUpper = entry.symbol.toUpperCase();
|
|
10477
|
-
const
|
|
10478
|
-
|
|
10600
|
+
const tokenAddrLower = entry.address.toLowerCase();
|
|
10601
|
+
let priceUsd;
|
|
10602
|
+
let valueUsd;
|
|
10603
|
+
if (symbolUpper.includes("USD")) {
|
|
10604
|
+
priceUsd = 1;
|
|
10605
|
+
valueUsd = balF64;
|
|
10606
|
+
} else if (tokenAddrLower === wrappedNative.toLowerCase()) {
|
|
10607
|
+
priceUsd = nativePriceUsd;
|
|
10608
|
+
valueUsd = balF64 * nativePriceUsd;
|
|
10609
|
+
} else {
|
|
10610
|
+
const px = priceByToken.get(tokenAddrLower);
|
|
10611
|
+
priceUsd = px ?? 0;
|
|
10612
|
+
valueUsd = px && px > 0 ? balF64 * px : 0;
|
|
10613
|
+
}
|
|
10479
10614
|
totalValueUsd += valueUsd;
|
|
10480
10615
|
tokens.push({
|
|
10481
10616
|
token: entry.address,
|
|
@@ -10516,6 +10651,23 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10516
10651
|
}
|
|
10517
10652
|
idx++;
|
|
10518
10653
|
}
|
|
10654
|
+
try {
|
|
10655
|
+
const client = createPublicClient24({ transport: http24(rpc) });
|
|
10656
|
+
const nativeBalance = await client.getBalance({ address: user });
|
|
10657
|
+
if (nativeBalance > 0n) {
|
|
10658
|
+
const nativeF64 = Number(nativeBalance) / 1e18;
|
|
10659
|
+
const nativeValueUsd = nativeF64 * nativePriceUsd;
|
|
10660
|
+
totalValueUsd += nativeValueUsd;
|
|
10661
|
+
tokens.push({
|
|
10662
|
+
token: wrappedNative,
|
|
10663
|
+
symbol: chain.native_token ?? "NATIVE",
|
|
10664
|
+
balance: nativeBalance,
|
|
10665
|
+
value_usd: nativeValueUsd,
|
|
10666
|
+
price_usd: nativePriceUsd
|
|
10667
|
+
});
|
|
10668
|
+
}
|
|
10669
|
+
} catch {
|
|
10670
|
+
}
|
|
10519
10671
|
return {
|
|
10520
10672
|
timestamp: Date.now(),
|
|
10521
10673
|
chain: chainName,
|
|
@@ -10633,6 +10785,10 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10633
10785
|
const calls = [];
|
|
10634
10786
|
const callLabels = [];
|
|
10635
10787
|
const tokenSymbols = (registry.tokens.get(chainName) ?? []).map((t) => t.symbol);
|
|
10788
|
+
const tokenAddrsByCallIdx = [];
|
|
10789
|
+
const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
|
|
10790
|
+
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
10791
|
+
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
10636
10792
|
for (const symbol of tokenSymbols) {
|
|
10637
10793
|
let entry;
|
|
10638
10794
|
try {
|
|
@@ -10646,6 +10802,7 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10646
10802
|
encodeFunctionData29({ abi: ERC20_ABI5, functionName: "balanceOf", args: [user] })
|
|
10647
10803
|
]);
|
|
10648
10804
|
callLabels.push(`balance:${symbol}`);
|
|
10805
|
+
tokenAddrsByCallIdx.push(entry.address);
|
|
10649
10806
|
}
|
|
10650
10807
|
const lendingProtocols = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending && p.interface === "aave_v3").filter((p) => p.contracts?.["pool"]);
|
|
10651
10808
|
for (const p of lendingProtocols) {
|
|
@@ -10655,10 +10812,16 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10655
10812
|
]);
|
|
10656
10813
|
callLabels.push(`lending:${p.name}`);
|
|
10657
10814
|
}
|
|
10658
|
-
const
|
|
10659
|
-
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
10660
|
-
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
10815
|
+
const priceTokens = [];
|
|
10661
10816
|
if (oracleAddr) {
|
|
10817
|
+
for (const tokenAddr of tokenAddrsByCallIdx) {
|
|
10818
|
+
calls.push([
|
|
10819
|
+
oracleAddr,
|
|
10820
|
+
encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [tokenAddr] })
|
|
10821
|
+
]);
|
|
10822
|
+
callLabels.push(`price:${tokenAddr}`);
|
|
10823
|
+
priceTokens.push(tokenAddr);
|
|
10824
|
+
}
|
|
10662
10825
|
calls.push([
|
|
10663
10826
|
oracleAddr,
|
|
10664
10827
|
encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [wrappedNative] })
|
|
@@ -10683,10 +10846,19 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10683
10846
|
printOutput({ error: `Multicall failed: ${errMsg(e)}` }, mode);
|
|
10684
10847
|
return;
|
|
10685
10848
|
}
|
|
10849
|
+
const balanceCallCount = tokenAddrsByCallIdx.length;
|
|
10850
|
+
const lendingCallCount = lendingProtocols.length;
|
|
10851
|
+
const priceStartIdx = balanceCallCount + lendingCallCount;
|
|
10686
10852
|
let nativePriceUsd = 0;
|
|
10853
|
+
const priceByToken = /* @__PURE__ */ new Map();
|
|
10687
10854
|
if (oracleAddr) {
|
|
10688
|
-
|
|
10689
|
-
|
|
10855
|
+
for (let i = 0; i < priceTokens.length; i++) {
|
|
10856
|
+
const priceData = results[priceStartIdx + i] ?? null;
|
|
10857
|
+
const px = Number(decodeU2562(priceData)) / 1e8;
|
|
10858
|
+
if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
|
|
10859
|
+
}
|
|
10860
|
+
const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
|
|
10861
|
+
nativePriceUsd = Number(decodeU2562(nativePriceData)) / 1e8;
|
|
10690
10862
|
}
|
|
10691
10863
|
let totalValueUsd = 0;
|
|
10692
10864
|
let idx = 0;
|
|
@@ -10705,12 +10877,21 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10705
10877
|
const decimals = entry.decimals;
|
|
10706
10878
|
const balF64 = Number(balance) / 10 ** decimals;
|
|
10707
10879
|
const symbolUpper = symbol.toUpperCase();
|
|
10708
|
-
const
|
|
10709
|
-
|
|
10880
|
+
const tokenAddrLower = entry.address.toLowerCase();
|
|
10881
|
+
let valueUsd;
|
|
10882
|
+
if (symbolUpper.includes("USD")) {
|
|
10883
|
+
valueUsd = balF64;
|
|
10884
|
+
} else if (tokenAddrLower === wrappedNative.toLowerCase()) {
|
|
10885
|
+
valueUsd = balF64 * nativePriceUsd;
|
|
10886
|
+
} else {
|
|
10887
|
+
const px = priceByToken.get(tokenAddrLower);
|
|
10888
|
+
valueUsd = px && px > 0 ? balF64 * px : null;
|
|
10889
|
+
}
|
|
10890
|
+
if (valueUsd !== null) totalValueUsd += valueUsd;
|
|
10710
10891
|
tokenBalances.push({
|
|
10711
10892
|
symbol,
|
|
10712
10893
|
balance: balF64.toFixed(4),
|
|
10713
|
-
value_usd: valueUsd.toFixed(2)
|
|
10894
|
+
value_usd: valueUsd !== null ? valueUsd.toFixed(2) : null
|
|
10714
10895
|
});
|
|
10715
10896
|
}
|
|
10716
10897
|
idx++;
|
|
@@ -10740,11 +10921,23 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10740
10921
|
}
|
|
10741
10922
|
idx++;
|
|
10742
10923
|
}
|
|
10924
|
+
let nativeBalance = 0n;
|
|
10925
|
+
let nativeValueUsd = 0;
|
|
10926
|
+
try {
|
|
10927
|
+
const client = createPublicClient25({ transport: http25(rpc) });
|
|
10928
|
+
nativeBalance = await client.getBalance({ address: user });
|
|
10929
|
+
const nativeF64 = Number(nativeBalance) / 1e18;
|
|
10930
|
+
nativeValueUsd = nativeF64 * nativePriceUsd;
|
|
10931
|
+
if (nativeBalance > 0n) totalValueUsd += nativeValueUsd;
|
|
10932
|
+
} catch {
|
|
10933
|
+
}
|
|
10743
10934
|
printOutput(
|
|
10744
10935
|
{
|
|
10745
10936
|
address: user,
|
|
10746
10937
|
chain: chain.name,
|
|
10747
10938
|
native_price_usd: nativePriceUsd.toFixed(2),
|
|
10939
|
+
native_balance: (Number(nativeBalance) / 1e18).toFixed(6),
|
|
10940
|
+
native_value_usd: nativeValueUsd.toFixed(2),
|
|
10748
10941
|
total_value_usd: totalValueUsd.toFixed(2),
|
|
10749
10942
|
token_balances: tokenBalances,
|
|
10750
10943
|
lending_positions: lendingPositions
|
|
@@ -11010,38 +11203,50 @@ function registerPrice(parent, getOpts) {
|
|
|
11010
11203
|
|
|
11011
11204
|
// src/commands/wallet.ts
|
|
11012
11205
|
init_dist();
|
|
11013
|
-
import { createPublicClient as
|
|
11206
|
+
import { createPublicClient as createPublicClient26, http as http26, formatEther } from "viem";
|
|
11207
|
+
function resolveCurrentAddress(override) {
|
|
11208
|
+
if (override) return { address: override, source: "flag" };
|
|
11209
|
+
try {
|
|
11210
|
+
const { address, signer } = resolveWalletWithSigner();
|
|
11211
|
+
return { address, source: signer ? "ows" : process.env["DEFI_PRIVATE_KEY"] ? "private_key" : "env" };
|
|
11212
|
+
} catch {
|
|
11213
|
+
return { address: null, source: "none" };
|
|
11214
|
+
}
|
|
11215
|
+
}
|
|
11014
11216
|
function registerWallet(parent, getOpts) {
|
|
11015
11217
|
const wallet = parent.command("wallet").description("Wallet management");
|
|
11016
|
-
wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
11218
|
+
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) => {
|
|
11017
11219
|
const chainName = requireChain(parent, getOpts);
|
|
11018
11220
|
if (!chainName) return;
|
|
11019
|
-
const addr = opts.address
|
|
11221
|
+
const { address: addr, source } = resolveCurrentAddress(opts.address);
|
|
11020
11222
|
if (!addr) {
|
|
11021
|
-
printOutput({
|
|
11223
|
+
printOutput({
|
|
11224
|
+
error: "No wallet configured. Set DEFI_WALLET_ADDRESS, set DEFI_PRIVATE_KEY, or pass --address."
|
|
11225
|
+
}, getOpts());
|
|
11022
11226
|
return;
|
|
11023
11227
|
}
|
|
11024
11228
|
const registry = Registry.loadEmbedded();
|
|
11025
11229
|
const chain = registry.getChain(chainName);
|
|
11026
|
-
const client =
|
|
11230
|
+
const client = createPublicClient26({ transport: http26(chain.effectiveRpcUrl()) });
|
|
11027
11231
|
const balance = await client.getBalance({ address: addr });
|
|
11028
11232
|
printOutput({
|
|
11029
11233
|
chain: chain.name,
|
|
11030
11234
|
address: addr,
|
|
11235
|
+
wallet_source: source,
|
|
11031
11236
|
native_token: chain.native_token,
|
|
11032
11237
|
balance_wei: balance,
|
|
11033
11238
|
balance_formatted: formatEther(balance)
|
|
11034
11239
|
}, getOpts());
|
|
11035
11240
|
});
|
|
11036
11241
|
wallet.command("address").description("Show configured wallet address").action(async () => {
|
|
11037
|
-
const
|
|
11038
|
-
printOutput({ address
|
|
11242
|
+
const { address, source } = resolveCurrentAddress();
|
|
11243
|
+
printOutput({ address, source }, getOpts());
|
|
11039
11244
|
});
|
|
11040
11245
|
}
|
|
11041
11246
|
|
|
11042
11247
|
// src/commands/token.ts
|
|
11043
11248
|
init_dist();
|
|
11044
|
-
import { createPublicClient as
|
|
11249
|
+
import { createPublicClient as createPublicClient27, http as http27, maxUint256 } from "viem";
|
|
11045
11250
|
function registerToken(parent, getOpts, makeExecutor2) {
|
|
11046
11251
|
const token = parent.command("token").description("Token operations: approve, allowance, transfer, balance");
|
|
11047
11252
|
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) => {
|
|
@@ -11054,7 +11259,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
11054
11259
|
}
|
|
11055
11260
|
const registry = Registry.loadEmbedded();
|
|
11056
11261
|
const chain = registry.getChain(chainName);
|
|
11057
|
-
const client =
|
|
11262
|
+
const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
|
|
11058
11263
|
const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
|
|
11059
11264
|
const [balance, symbol, decimals] = await Promise.all([
|
|
11060
11265
|
client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "balanceOf", args: [owner] }),
|
|
@@ -11090,7 +11295,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
11090
11295
|
}
|
|
11091
11296
|
const registry = Registry.loadEmbedded();
|
|
11092
11297
|
const chain = registry.getChain(chainName);
|
|
11093
|
-
const client =
|
|
11298
|
+
const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
|
|
11094
11299
|
const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
|
|
11095
11300
|
const allowance = await client.readContract({
|
|
11096
11301
|
address: tokenAddr,
|
|
@@ -11208,6 +11413,17 @@ var CCTP_USDC_ADDRESSES = {
|
|
|
11208
11413
|
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
11209
11414
|
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
|
|
11210
11415
|
};
|
|
11416
|
+
function cctpMinFeeGuard(amountWei, maxFeeSubunits, feeUsdc) {
|
|
11417
|
+
if (amountWei <= maxFeeSubunits) {
|
|
11418
|
+
const amountUsdc = Number(amountWei) / 1e6;
|
|
11419
|
+
return {
|
|
11420
|
+
error: `CCTP: amount ${amountWei.toString()} (${amountUsdc} USDC) is below the minimum bridge fee of ${maxFeeSubunits} (${feeUsdc} USDC). Increase --amount.`,
|
|
11421
|
+
minimum_amount_wei: maxFeeSubunits.toString(),
|
|
11422
|
+
minimum_amount_usdc: feeUsdc
|
|
11423
|
+
};
|
|
11424
|
+
}
|
|
11425
|
+
return null;
|
|
11426
|
+
}
|
|
11211
11427
|
async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
|
|
11212
11428
|
try {
|
|
11213
11429
|
const res = await fetch(`${CCTP_FEE_API}/${srcDomain}/${dstDomain}`);
|
|
@@ -11296,12 +11512,9 @@ function registerBridge(parent, getOpts) {
|
|
|
11296
11512
|
}
|
|
11297
11513
|
const amountUsdc = Number(BigInt(opts.amount)) / 1e6;
|
|
11298
11514
|
const { fee, maxFeeSubunits } = await getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc);
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
minimum_amount_wei: maxFeeSubunits.toString(),
|
|
11303
|
-
minimum_amount_usdc: fee
|
|
11304
|
-
}, getOpts());
|
|
11515
|
+
const guardErr = cctpMinFeeGuard(BigInt(opts.amount), maxFeeSubunits, fee);
|
|
11516
|
+
if (guardErr) {
|
|
11517
|
+
printOutput(guardErr, getOpts());
|
|
11305
11518
|
return;
|
|
11306
11519
|
}
|
|
11307
11520
|
const recipientPadded = `0x${"0".repeat(24)}${recipient.replace("0x", "").toLowerCase()}`;
|
|
@@ -11582,12 +11795,14 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
11582
11795
|
slippagePct,
|
|
11583
11796
|
wallet
|
|
11584
11797
|
);
|
|
11798
|
+
const fromLower = fromAddr.toLowerCase();
|
|
11799
|
+
const isNativeInput = fromLower === "0x0000000000000000000000000000000000000000" || fromLower === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
11585
11800
|
const tx = {
|
|
11586
11801
|
description: `OpenOcean: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
|
|
11587
11802
|
to: swap.to,
|
|
11588
11803
|
data: swap.data,
|
|
11589
11804
|
value: parseBigIntValue(swap.value),
|
|
11590
|
-
approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }]
|
|
11805
|
+
...isNativeInput ? {} : { approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }] }
|
|
11591
11806
|
};
|
|
11592
11807
|
const result = await executor.execute(tx);
|
|
11593
11808
|
printOutput({
|
|
@@ -11679,7 +11894,8 @@ import pc2 from "picocolors";
|
|
|
11679
11894
|
import { createInterface } from "readline";
|
|
11680
11895
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
11681
11896
|
import { resolve as resolve4 } from "path";
|
|
11682
|
-
|
|
11897
|
+
import { homedir as homedir2 } from "os";
|
|
11898
|
+
var DEFI_DIR = resolve4(homedir2(), ".defi");
|
|
11683
11899
|
var ENV_FILE = resolve4(DEFI_DIR, ".env");
|
|
11684
11900
|
function ensureDefiDir() {
|
|
11685
11901
|
if (!existsSync3(DEFI_DIR)) mkdirSync2(DEFI_DIR, { recursive: true, mode: 448 });
|
|
@@ -11714,6 +11930,44 @@ function writeEnvFile(env) {
|
|
|
11714
11930
|
function ask(rl, question) {
|
|
11715
11931
|
return new Promise((res) => rl.question(question, (answer) => res(answer.trim())));
|
|
11716
11932
|
}
|
|
11933
|
+
function askSecret(rl, question) {
|
|
11934
|
+
const stdin = process.stdin;
|
|
11935
|
+
if (!stdin.isTTY) return ask(rl, question);
|
|
11936
|
+
process.stdout.write(question);
|
|
11937
|
+
const rlAny = rl;
|
|
11938
|
+
const original = rlAny._writeToOutput;
|
|
11939
|
+
rlAny._writeToOutput = (s) => {
|
|
11940
|
+
if (s.includes("\n") || s.includes("\r")) {
|
|
11941
|
+
original?.call(rl, s);
|
|
11942
|
+
}
|
|
11943
|
+
};
|
|
11944
|
+
return new Promise((res) => {
|
|
11945
|
+
rl.question("", (answer) => {
|
|
11946
|
+
rlAny._writeToOutput = original;
|
|
11947
|
+
process.stdout.write("\n");
|
|
11948
|
+
res(answer.trim());
|
|
11949
|
+
});
|
|
11950
|
+
});
|
|
11951
|
+
}
|
|
11952
|
+
function maskRpcUrl(s) {
|
|
11953
|
+
try {
|
|
11954
|
+
const u = new URL(s);
|
|
11955
|
+
if (u.pathname && u.pathname !== "/" && u.pathname.length > 1) {
|
|
11956
|
+
return `${u.protocol}//${u.host}/***`;
|
|
11957
|
+
}
|
|
11958
|
+
return `${u.protocol}//${u.host}`;
|
|
11959
|
+
} catch {
|
|
11960
|
+
return "***";
|
|
11961
|
+
}
|
|
11962
|
+
}
|
|
11963
|
+
function isValidRpcUrl(s) {
|
|
11964
|
+
try {
|
|
11965
|
+
const u = new URL(s);
|
|
11966
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
11967
|
+
} catch {
|
|
11968
|
+
return false;
|
|
11969
|
+
}
|
|
11970
|
+
}
|
|
11717
11971
|
function isValidAddress(s) {
|
|
11718
11972
|
return /^0x[0-9a-fA-F]{40}$/.test(s);
|
|
11719
11973
|
}
|
|
@@ -11738,7 +11992,14 @@ function registerSetup(program2) {
|
|
|
11738
11992
|
if (Object.keys(existing).length > 0) {
|
|
11739
11993
|
console.log(pc2.white(" Current configuration:"));
|
|
11740
11994
|
for (const [key, value] of Object.entries(existing)) {
|
|
11741
|
-
|
|
11995
|
+
let masked;
|
|
11996
|
+
if (key.toLowerCase().includes("key")) {
|
|
11997
|
+
masked = value.slice(0, 6) + "..." + value.slice(-4);
|
|
11998
|
+
} else if (key.endsWith("RPC_URL")) {
|
|
11999
|
+
masked = maskRpcUrl(value);
|
|
12000
|
+
} else {
|
|
12001
|
+
masked = value;
|
|
12002
|
+
}
|
|
11742
12003
|
console.log(` ${pc2.cyan(key.padEnd(24))} ${pc2.gray(masked)}`);
|
|
11743
12004
|
}
|
|
11744
12005
|
console.log();
|
|
@@ -11752,7 +12013,7 @@ function registerSetup(program2) {
|
|
|
11752
12013
|
}
|
|
11753
12014
|
const newEnv = {};
|
|
11754
12015
|
console.log(pc2.cyan(pc2.bold(" Wallet")));
|
|
11755
|
-
const privateKey = await
|
|
12016
|
+
const privateKey = await askSecret(rl, " Private key (optional, for --broadcast, 0x... \u2014 input hidden): ");
|
|
11756
12017
|
if (privateKey) {
|
|
11757
12018
|
const normalized = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
|
|
11758
12019
|
if (!isValidPrivateKey(normalized)) {
|
|
@@ -11788,6 +12049,10 @@ function registerSetup(program2) {
|
|
|
11788
12049
|
for (const { env, label } of rpcPrompts) {
|
|
11789
12050
|
const value = await ask(rl, ` ${label} RPC URL: `);
|
|
11790
12051
|
if (value) {
|
|
12052
|
+
if (!isValidRpcUrl(value)) {
|
|
12053
|
+
console.log(pc2.yellow(` Invalid URL (${value}). Skipped \u2014 re-run setup to retry.`));
|
|
12054
|
+
continue;
|
|
12055
|
+
}
|
|
11791
12056
|
newEnv[env] = value;
|
|
11792
12057
|
console.log(` ${pc2.green("OK")} ${label} RPC set`);
|
|
11793
12058
|
}
|
|
@@ -11811,7 +12076,7 @@ function registerSetup(program2) {
|
|
|
11811
12076
|
];
|
|
11812
12077
|
for (const [k, label] of rpcSummary) {
|
|
11813
12078
|
const v = finalEnv[k];
|
|
11814
|
-
if (v) console.log(` ${label}: ${pc2.gray(v)}`);
|
|
12079
|
+
if (v) console.log(` ${label}: ${pc2.gray(maskRpcUrl(v))}`);
|
|
11815
12080
|
}
|
|
11816
12081
|
console.log(pc2.bold(pc2.white("\n Next steps:")));
|
|
11817
12082
|
console.log(` ${pc2.green("defi portfolio")} view balances & positions`);
|
|
@@ -11830,11 +12095,11 @@ function registerSetup(program2) {
|
|
|
11830
12095
|
// src/commands/ows.ts
|
|
11831
12096
|
import pc3 from "picocolors";
|
|
11832
12097
|
init_dist();
|
|
11833
|
-
import { createPublicClient as
|
|
12098
|
+
import { createPublicClient as createPublicClient28, http as http28, formatEther as formatEther2 } from "viem";
|
|
11834
12099
|
async function getEvmBalance(address, chainName) {
|
|
11835
12100
|
const registry = Registry.loadEmbedded();
|
|
11836
12101
|
const chain = registry.getChain(chainName);
|
|
11837
|
-
const client =
|
|
12102
|
+
const client = createPublicClient28({ transport: http28(chain.effectiveRpcUrl()) });
|
|
11838
12103
|
const balance = await client.getBalance({ address });
|
|
11839
12104
|
return {
|
|
11840
12105
|
native_token: chain.native_token,
|