- Update dbis_core, cross-chain-pmm-lps, explorer-monorepo, metamask-integration, pr-workspace/chains - Omit embedded publish git dirs and empty placeholders from index Made-with: Cursor
4635 lines
177 KiB
JavaScript
Executable File
4635 lines
177 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* PMM / reserve-style flash-loan "push trade": slippage, VWAP, PnL, break-even external price,
|
|
* and quote-seeding / liquidity-loop planning.
|
|
*
|
|
* Matches the quote-in → base-out fallback used in DODOPMMProvider._fallbackReserveQuote:
|
|
* amountOut = (netAmountIn * baseReserve) / (quoteReserve + netAmountIn)
|
|
* (see smom-dbis-138/contracts/liquidity/providers/DODOPMMProvider.sol)
|
|
*
|
|
* Not a full DODO PMM (i, k, oracle) simulation — use pool querySellQuote on-chain for production.
|
|
* Amounts: use consistent token base units (e.g. USDC 6 decimals, so 1 USDC = 1_000_000 raw units).
|
|
* JS Number is safe below ~9e15.
|
|
*
|
|
* -----------------------------------------------------------------------------
|
|
* Ethereum Mainnet — live cWUSDC / USDC PMM (repo registry)
|
|
* -----------------------------------------------------------------------------
|
|
* "xWUSDC" in ops shorthand = **cWUSDC** (compliant wrapped USDC on public mesh).
|
|
* Canonical addresses + pool row: cross-chain-pmm-lps/config/deployment-status.json (chain "1").
|
|
*
|
|
* **Funded loop (not necessarily Aave flash):** USDC working capital x funds:
|
|
* (1) USDC → ETH on Uniswap (or other deep venue) — fees + slippage → factor f_eth
|
|
* (2) ETH → cWUSDC — often extra hop if no WETH/cWUSDC book; fold into composite acquisition cost
|
|
* (3) cWUSDC → USDC on DODO PMM — **sell base** leg:
|
|
* quoteOut = (netBaseIn * Q) / (B + netBaseIn) [sellBase branch]
|
|
* Model the first two legs as a single **effective USDC per cWUSDC** you need before the PMM exit,
|
|
* or run this script on the PMM leg only with -B/-Q from getVaultReserve().
|
|
*
|
|
* **Deepening the pool with cWUSDC:** increases base reserve B. For **USDC → cWUSDC** (quote in),
|
|
* larger B → **more** Δy and **lower** VWAP for the same x (tighter book for buyers). For **cWUSDC → USDC**
|
|
* (base in), larger B → **less** USDC per unit base for a large sell (pool absorbs more base without
|
|
* draining quote). Size both sides for balanced inventory if you route flow in both directions.
|
|
*
|
|
* -----------------------------------------------------------------------------
|
|
* Baseline recalc (vs liquidity-ratio heuristic)
|
|
* -----------------------------------------------------------------------------
|
|
* Reserves B = Q = 10e6 (10M each side). Gross quote in x = 10e6.
|
|
*
|
|
* Zero LP fee:
|
|
* Δy = B * x / (Q + x) = 10M * 10M / 20M = 5M base out
|
|
* VWAP = x / Δy = 2.00 quote per base (not ~1.25 from impact/2 linearization)
|
|
*
|
|
* With DEFAULT_LP_FEE = 3 bps (netIn = x * 9997/10000):
|
|
* Δy slightly below 5M; VWAP slightly above 2.00
|
|
*
|
|
* x = 2e6 quote in, B = Q = 10e6, no LP fee:
|
|
* Δy = 2 * 10 / 12 M ≈ 1.667M, VWAP ≈ 1.20 (not ~1.083)
|
|
*
|
|
* -----------------------------------------------------------------------------
|
|
* Break-even (plug-in)
|
|
* -----------------------------------------------------------------------------
|
|
* Let x = gross quote swapped in, f_lp = LP fee on pool input (fraction of x),
|
|
* Δx_net = x * (1 - f_lp) [same as contract: netAmountIn]
|
|
* Δy = B * Δx_net / (Q + Δx_net)
|
|
* Flash fee f_flash on repayment: repay = x * (1 + f_flash)
|
|
* Effective external proceeds: model exit DEX fee / impact as P_eff = P_ext * (1 - f_exit)
|
|
*
|
|
* Π = P_eff * Δy - x * (1 + f_flash)
|
|
*
|
|
* Break-even spot P_ext (given exit fee on sell proceeds):
|
|
* P_ext* = x * (1 + f_flash) / (Δy * (1 - f_exit))
|
|
*
|
|
* Fold routing into one number: set --external-price to your net USDC per base after all exits.
|
|
*
|
|
* Usage:
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs --examples
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs -B 10000000 -Q 10000000 -x 10000000 \
|
|
* --lp-fee-bps 3 --flash-fee-bps 5 --external-price 1.0
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs -B 10e6 -Q 10e6 --scan 1e6,2e6,5e6,10e6 \
|
|
* --lp-fee-bps 3 --flash-fee-bps 5
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs --mainnet-map
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs --mainnet-map --compare-deepen 5e6 \
|
|
* -B 2000000 -Q 2000000 --scan 500000,1000000,2000000 --flash-fee-bps 0
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs -B 4000000 -Q 12000000 \
|
|
* --seed-pools 6 --target-quote-per-pool 500000000000 --lp-fee-bps 10 --inventory-base 8000000
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs --external-price 0.992 \
|
|
* --seed-pools 6 --target-quote-per-pool 500000000000 --exit-fee-bps 20 --inventory-base 4000000
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs -B 4854640 -Q 4854839 \
|
|
* --inventory-loop-scan --scan 50000,100000,150000,200000,250000 --lp-fee-bps 10 \
|
|
* --flash-fee-bps 5 --min-retained-usdc 10000
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs -B 4854640 -Q 4854839 \
|
|
* --inventory-loop-rank --scan 50000,100000,150000,200000,250000 --lp-fee-bps 10 \
|
|
* --flash-fee-bps 5 --min-retained-usdc 10000 --target-flash-borrow 90000
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs -B 4854640 -Q 4854839 \
|
|
* --inventory-loop-rank --scan 50000,100000,150000 --lp-fee-bps 10 --flash-fee-bps 5 \
|
|
* --min-retained-usdc 10000 --inventory-base 1000000 --gas-tx-count 3 --gas-per-tx 250000 \
|
|
* --max-fee-gwei 40 --native-token-price 3200 --max-post-trade-deviation-bps 250 \
|
|
* --max-cw-burn-fraction 0.1 --cooldown-blocks 12 --rank-profile conservative
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs -B 4854640 -Q 4854839 \
|
|
* --inventory-loop-rank --scan 100000,250000,500000 --lp-fee-bps 10 --flash-fee-bps 5 \
|
|
* --target-flash-borrow 1000000 --flash-add-to-pool
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs -B 4854640 -Q 4854839 -x 1000000 \
|
|
* --full-loop-dry-run --external-price 1.01 --flash-fee-bps 5 --lp-fee-bps 10 \
|
|
* --seed-pools 3 --target-quote-per-pool 250000 --gas-tx-count 3 --gas-per-tx 250000 \
|
|
* --max-fee-gwei 40 --native-token-price 3200 --max-post-trade-deviation-bps 500
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs -B 4854640 -Q 4854839 -x 1000000 \
|
|
* --full-loop-dry-run --owned-base-add 500000 --external-exit-price 1.12 --flash-fee-bps 5 \
|
|
* --lp-fee-bps 10 --base-asset cWUSDC --quote-asset USDC
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs --public-live-sweep --loop-strategy both \
|
|
* --external-exit-price 1.12 --external-entry-price 0.74 --external-via-asset ETH \
|
|
* --gas-tx-count 3 --gas-per-tx 250000 --max-fee-gwei 40 --native-token-price 3200 \
|
|
* --max-post-trade-deviation-bps 500
|
|
* node scripts/analytics/pmm-flash-push-break-even.mjs --sequential-matched-loops 100 \
|
|
* -B 256763253 -Q 256763253 --loop-strategy quote-push \
|
|
* --base-asset cWUSDC --quote-decimals 6 --quote-asset USDC \
|
|
* --external-exit-price 1.12 --lp-fee-bps 3 --flash-fee-bps 5 \
|
|
* --gas-tx-count 3 --gas-per-tx 185000 --max-fee-gwei 60 --native-token-price 3200 \
|
|
* --max-post-trade-deviation-bps 500 --oracle-price 1 --flash-provider-cap 1000000000000
|
|
*/
|
|
|
|
import { parseArgs } from 'node:util'
|
|
import { readFileSync, existsSync, readdirSync } from 'node:fs'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { execFileSync } from 'node:child_process'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
const REPO_ROOT = resolve(__dirname, '../..')
|
|
const DEPLOYMENT_STATUS = resolve(REPO_ROOT, 'cross-chain-pmm-lps/config/deployment-status.json')
|
|
const SMART_CONTRACTS_MASTER = resolve(REPO_ROOT, 'config/smart-contracts-master.json')
|
|
const DEPLOYER_TOKEN_AUDIT_DIR = resolve(REPO_ROOT, 'reports/deployer-token-audit')
|
|
const DEFAULT_MAINNET_BALANCER_VAULT =
|
|
process.env.MAINNET_BALANCER_VAULT ?? '0xBA12222222228d8Ba445958a75a0704d566BF2C8'
|
|
const DEFAULT_MAINNET_AAVE_V3_POOL =
|
|
process.env.MAINNET_AAVE_V3_POOL ?? '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2'
|
|
const DEFAULT_MAINNET_AAVE_V3_PROVIDER =
|
|
process.env.MAINNET_AAVE_V3_POOL_ADDRESSES_PROVIDER ?? '0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e'
|
|
|
|
function netQuoteIn(grossX, lpFeeBps) {
|
|
const bps = Math.min(10000, Math.max(0, lpFeeBps))
|
|
return (grossX * (10000 - bps)) / 10000
|
|
}
|
|
|
|
function grossInputForNet(netIn, lpFeeBps) {
|
|
const bps = Math.min(10000, Math.max(0, lpFeeBps))
|
|
const mult = (10000 - bps) / 10000
|
|
if (mult <= 0) return Infinity
|
|
return netIn / mult
|
|
}
|
|
|
|
/** Quote in → base out (same branch as sellBase === false in DODOPMMProvider) */
|
|
function quoteInToBaseOut(baseReserve, quoteReserve, grossQuoteIn, lpFeeBps) {
|
|
if (baseReserve <= 0 || quoteReserve <= 0 || grossQuoteIn <= 0) {
|
|
return { deltaXNet: 0, baseOut: 0, vwap: NaN }
|
|
}
|
|
const deltaXNet = netQuoteIn(grossQuoteIn, lpFeeBps)
|
|
if (deltaXNet <= 0) return { deltaXNet: 0, baseOut: 0, vwap: NaN }
|
|
const baseOut = (deltaXNet * baseReserve) / (quoteReserve + deltaXNet)
|
|
const vwap = grossQuoteIn / baseOut
|
|
return { deltaXNet, baseOut, vwap }
|
|
}
|
|
|
|
/** Base in → quote out (sellBase branch in DODOPMMProvider) */
|
|
function baseInToQuoteOut(baseReserve, quoteReserve, grossBaseIn, lpFeeBps) {
|
|
if (baseReserve <= 0 || quoteReserve <= 0 || grossBaseIn <= 0) {
|
|
return { deltaBaseNet: 0, quoteOut: 0, vwapQuotePerBase: NaN }
|
|
}
|
|
const deltaBaseNet = netQuoteIn(grossBaseIn, lpFeeBps)
|
|
if (deltaBaseNet <= 0) return { deltaBaseNet: 0, quoteOut: 0, vwapQuotePerBase: NaN }
|
|
const quoteOut = (deltaBaseNet * quoteReserve) / (baseReserve + deltaBaseNet)
|
|
const vwapQuotePerBase = quoteOut / grossBaseIn
|
|
return { deltaBaseNet, quoteOut, vwapQuotePerBase }
|
|
}
|
|
|
|
/** Inverse of sell-base branch: required base in to harvest a target quote amount */
|
|
function baseInRequiredForQuoteOut(baseReserve, quoteReserve, targetQuoteOut, lpFeeBps) {
|
|
if (baseReserve <= 0 || quoteReserve <= 0 || targetQuoteOut <= 0) {
|
|
return {
|
|
feasible: false,
|
|
deltaBaseNet: 0,
|
|
grossBaseIn: 0,
|
|
feeBase: 0,
|
|
vwapQuotePerBase: NaN,
|
|
}
|
|
}
|
|
if (targetQuoteOut >= quoteReserve) {
|
|
return {
|
|
feasible: false,
|
|
deltaBaseNet: Infinity,
|
|
grossBaseIn: Infinity,
|
|
feeBase: Infinity,
|
|
vwapQuotePerBase: 0,
|
|
}
|
|
}
|
|
const deltaBaseNet = (targetQuoteOut * baseReserve) / (quoteReserve - targetQuoteOut)
|
|
const grossBaseIn = grossInputForNet(deltaBaseNet, lpFeeBps)
|
|
const feeBase = grossBaseIn - deltaBaseNet
|
|
const vwapQuotePerBase = targetQuoteOut / grossBaseIn
|
|
return { feasible: true, deltaBaseNet, grossBaseIn, feeBase, vwapQuotePerBase }
|
|
}
|
|
|
|
function loadMainnetCwUsdcUsdc() {
|
|
if (!existsSync(DEPLOYMENT_STATUS)) return null
|
|
const raw = JSON.parse(readFileSync(DEPLOYMENT_STATUS, 'utf8'))
|
|
const chain = raw.chains?.['1']
|
|
if (!chain?.pmmPools || !chain.cwTokens) return null
|
|
const row = chain.pmmPools.find((p) => p.base === 'cWUSDC' && p.quote === 'USDC')
|
|
if (!row) return null
|
|
const cWUSDC = chain.cwTokens.cWUSDC
|
|
const USDC = chain.anchorAddresses?.USDC
|
|
return { row, cWUSDC, USDC, feeBps: row.feeBps ?? 10, poolAddress: row.poolAddress }
|
|
}
|
|
|
|
function loadMainnetPool(base, quote) {
|
|
if (!existsSync(DEPLOYMENT_STATUS)) return null
|
|
const raw = JSON.parse(readFileSync(DEPLOYMENT_STATUS, 'utf8'))
|
|
const chain = raw.chains?.['1']
|
|
if (!chain?.pmmPools) return null
|
|
const row = chain.pmmPools.find((p) => p.base === base && p.quote === quote)
|
|
if (!row) return null
|
|
return {
|
|
row,
|
|
chain,
|
|
baseAddress: chain.cwTokens?.[base] ?? chain.anchorAddresses?.[base] ?? null,
|
|
quoteAddress: chain.anchorAddresses?.[quote] ?? chain.cwTokens?.[quote] ?? null,
|
|
feeBps: row.feeBps ?? 10,
|
|
poolAddress: row.poolAddress,
|
|
}
|
|
}
|
|
|
|
function loadLiveMainnetPublicUsdPools() {
|
|
if (!existsSync(DEPLOYMENT_STATUS)) return []
|
|
const raw = JSON.parse(readFileSync(DEPLOYMENT_STATUS, 'utf8'))
|
|
const chain = raw.chains?.['1']
|
|
if (!chain?.pmmPools) return []
|
|
return chain.pmmPools
|
|
.filter(
|
|
(row) =>
|
|
['cWUSDT', 'cWUSDC'].includes(row.base) &&
|
|
['USDC', 'USDT'].includes(row.quote) &&
|
|
row.poolAddress,
|
|
)
|
|
.map((row) => ({
|
|
...row,
|
|
baseAddress: chain.cwTokens?.[row.base] ?? null,
|
|
quoteAddress: chain.anchorAddresses?.[row.quote] ?? null,
|
|
feeBps: row.feeBps ?? 10,
|
|
}))
|
|
}
|
|
|
|
function loadChain138TokenAddress(symbol) {
|
|
if (!existsSync(SMART_CONTRACTS_MASTER)) return null
|
|
const raw = JSON.parse(readFileSync(SMART_CONTRACTS_MASTER, 'utf8'))
|
|
return raw.chains?.['138']?.contracts?.[symbol] ?? null
|
|
}
|
|
|
|
function inferSourceAsset(inventoryAsset) {
|
|
const match = /^cW(.+)$/.exec(inventoryAsset)
|
|
return match ? `c${match[1]}` : inventoryAsset
|
|
}
|
|
|
|
function latestBalanceReportPath() {
|
|
if (!existsSync(DEPLOYER_TOKEN_AUDIT_DIR)) return null
|
|
const entries = readdirSync(DEPLOYER_TOKEN_AUDIT_DIR)
|
|
.filter((name) => /^deployer-token-balances-.*\.json$/.test(name))
|
|
.sort()
|
|
if (entries.length === 0) return null
|
|
return resolve(DEPLOYER_TOKEN_AUDIT_DIR, entries[entries.length - 1])
|
|
}
|
|
|
|
function loadBalanceReport(reportPath) {
|
|
if (!reportPath || !existsSync(reportPath)) return null
|
|
return JSON.parse(readFileSync(reportPath, 'utf8'))
|
|
}
|
|
|
|
function sumReportBalance(report, chainId, symbol) {
|
|
if (!report?.balances) return null
|
|
let total = 0
|
|
let matched = false
|
|
for (const row of report.balances) {
|
|
if (
|
|
row?.chain_id === chainId &&
|
|
row?.token_symbol === symbol &&
|
|
row?.query_status === 'ok' &&
|
|
row?.balance_raw != null &&
|
|
row?.decimals != null
|
|
) {
|
|
total += Number(row.balance_raw) / 10 ** Number(row.decimals)
|
|
matched = true
|
|
}
|
|
}
|
|
return matched ? total : null
|
|
}
|
|
|
|
function sumReportBalanceInfo(report, chainId, symbol) {
|
|
if (!report?.balances) return null
|
|
let totalRaw = 0
|
|
let decimals = null
|
|
let matched = false
|
|
for (const row of report.balances) {
|
|
if (
|
|
row?.chain_id === chainId &&
|
|
row?.token_symbol === symbol &&
|
|
row?.query_status === 'ok' &&
|
|
row?.balance_raw != null &&
|
|
row?.decimals != null
|
|
) {
|
|
const rowDecimals = Number(row.decimals)
|
|
if (!Number.isFinite(rowDecimals)) continue
|
|
if (decimals == null) decimals = rowDecimals
|
|
if (decimals !== rowDecimals) return null
|
|
totalRaw += Number(row.balance_raw)
|
|
matched = true
|
|
}
|
|
}
|
|
if (!matched || decimals == null) return null
|
|
return {
|
|
balance: totalRaw / 10 ** decimals,
|
|
raw: totalRaw,
|
|
decimals,
|
|
}
|
|
}
|
|
|
|
function rpcUrlForChain(chainId) {
|
|
switch (chainId) {
|
|
case 1:
|
|
return process.env.ETHEREUM_MAINNET_RPC ?? process.env.ETH_MAINNET_RPC ?? null
|
|
case 138:
|
|
return process.env.CHAIN138_RPC_URL ?? process.env.RPC_URL_138 ?? null
|
|
default:
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryReadTokenDecimalsViaCast(chainId, tokenAddress) {
|
|
const rpcUrl = rpcUrlForChain(chainId)
|
|
if (!rpcUrl || !tokenAddress) return null
|
|
try {
|
|
const raw = execFileSync(
|
|
'cast',
|
|
['call', tokenAddress, 'decimals()(uint8)', '--rpc-url', rpcUrl],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
const decimals = Number(parseCastScalar(raw))
|
|
return Number.isFinite(decimals) ? decimals : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function parseCastScalar(raw) {
|
|
if (!raw) return null
|
|
const token = String(raw).trim().split(/\s+/)[0]
|
|
if (token === '') return null
|
|
return token
|
|
}
|
|
|
|
function extractNthAddress(raw, index) {
|
|
if (!raw || !(index > 0)) return null
|
|
const matches = String(raw).match(/0x[a-fA-F0-9]{40}/g) ?? []
|
|
return matches[index - 1] ?? null
|
|
}
|
|
|
|
function tryCastCallRaw(address, signature, args, rpcUrl) {
|
|
if (!address || !signature || !rpcUrl) return null
|
|
try {
|
|
return execFileSync(
|
|
'cast',
|
|
['call', address, signature, ...args.map(String), '--rpc-url', rpcUrl],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
).trim()
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryReadCastCode(address, rpcUrl) {
|
|
if (!address || !rpcUrl) return null
|
|
try {
|
|
return execFileSync('cast', ['code', address, '--rpc-url', rpcUrl], {
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
}).trim()
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryReadTokenRawBalanceViaCast(rpcUrl, tokenAddress, walletAddress) {
|
|
if (!rpcUrl || !tokenAddress || !walletAddress) return null
|
|
const raw = tryCastCallRaw(tokenAddress, 'balanceOf(address)(uint256)', [walletAddress], rpcUrl)
|
|
const scalar = raw != null ? Number(parseCastScalar(raw)) : NaN
|
|
return Number.isFinite(scalar) ? scalar : null
|
|
}
|
|
|
|
function resolveDeployerAddressViaCast() {
|
|
if (!process.env.PRIVATE_KEY) return null
|
|
try {
|
|
return execFileSync('cast', ['wallet', 'address', '--private-key', process.env.PRIVATE_KEY], {
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
}).trim()
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryReadPoolReservesViaCast(chainId, poolAddress) {
|
|
const rpcUrl = rpcUrlForChain(chainId)
|
|
if (!rpcUrl || !poolAddress) return null
|
|
try {
|
|
const out = execFileSync(
|
|
'cast',
|
|
['call', poolAddress, 'getVaultReserve()(uint256,uint256)', '--rpc-url', rpcUrl],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
.trim()
|
|
.split(/\r?\n/)
|
|
.map((line) => parseCastScalar(line))
|
|
.filter(Boolean)
|
|
if (out.length < 2) return null
|
|
return {
|
|
baseReserve: Number(out[0]),
|
|
quoteReserve: Number(out[1]),
|
|
source: `live cast via chain ${chainId} RPC`,
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryReadTokenBalanceViaCast(chainId, tokenAddress, walletAddress) {
|
|
const rpcUrl = rpcUrlForChain(chainId)
|
|
if (!rpcUrl || !tokenAddress || !walletAddress) return null
|
|
try {
|
|
const decimalsRaw = execFileSync(
|
|
'cast',
|
|
['call', tokenAddress, 'decimals()(uint8)', '--rpc-url', rpcUrl],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
const balanceRaw = execFileSync(
|
|
'cast',
|
|
['call', tokenAddress, 'balanceOf(address)(uint256)', walletAddress, '--rpc-url', rpcUrl],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
const decimals = Number(parseCastScalar(decimalsRaw))
|
|
const raw = Number(parseCastScalar(balanceRaw))
|
|
if (!Number.isFinite(decimals) || !Number.isFinite(raw)) return null
|
|
return {
|
|
balance: raw / 10 ** decimals,
|
|
decimals,
|
|
raw,
|
|
source: `live cast via chain ${chainId} RPC`,
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryReadPoolFeeBpsViaCast(chainId, poolAddress) {
|
|
const rpcUrl = rpcUrlForChain(chainId)
|
|
if (!rpcUrl || !poolAddress) return null
|
|
try {
|
|
const raw = execFileSync(
|
|
'cast',
|
|
['call', poolAddress, '_LP_FEE_RATE_()(uint256)', '--rpc-url', rpcUrl],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
const feeBps = Number(parseCastScalar(raw))
|
|
return Number.isFinite(feeBps) ? feeBps : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryProbePoolQuoteSurfaceViaCast(chainId, poolAddress, traderAddress, probeAmount = 1_000_000) {
|
|
const rpcUrl = rpcUrlForChain(chainId)
|
|
if (!rpcUrl || !poolAddress || !traderAddress || !(probeAmount > 0)) return null
|
|
const baseToken = tryCastCallRaw(poolAddress, '_BASE_TOKEN_()(address)', [], rpcUrl)
|
|
const quoteToken = tryCastCallRaw(poolAddress, '_QUOTE_TOKEN_()(address)', [], rpcUrl)
|
|
const midPrice = tryCastCallRaw(poolAddress, 'getMidPrice()(uint256)', [], rpcUrl)
|
|
let sellBaseOk = false
|
|
let sellQuoteOk = false
|
|
let integrationSellBaseOk = false
|
|
let integrationSellQuoteOk = false
|
|
|
|
try {
|
|
execFileSync(
|
|
'cast',
|
|
[
|
|
'call',
|
|
poolAddress,
|
|
'querySellBase(address,uint256)(uint256,uint256)',
|
|
traderAddress,
|
|
String(probeAmount),
|
|
'--rpc-url',
|
|
rpcUrl,
|
|
],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
sellBaseOk = true
|
|
} catch {}
|
|
|
|
try {
|
|
execFileSync(
|
|
'cast',
|
|
[
|
|
'call',
|
|
poolAddress,
|
|
'querySellQuote(address,uint256)(uint256,uint256)',
|
|
traderAddress,
|
|
String(probeAmount),
|
|
'--rpc-url',
|
|
rpcUrl,
|
|
],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
sellQuoteOk = true
|
|
} catch {}
|
|
|
|
if (chainId === 1 && baseToken && quoteToken) {
|
|
const integrationAddress = process.env.DODO_PMM_INTEGRATION_MAINNET ?? null
|
|
if (integrationAddress) {
|
|
try {
|
|
execFileSync(
|
|
'cast',
|
|
[
|
|
'call',
|
|
integrationAddress,
|
|
'swapExactIn(address,address,uint256,uint256)(uint256)',
|
|
poolAddress,
|
|
String(parseCastScalar(quoteToken)),
|
|
String(probeAmount),
|
|
'1',
|
|
'--from',
|
|
traderAddress,
|
|
'--rpc-url',
|
|
rpcUrl,
|
|
],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
integrationSellQuoteOk = true
|
|
} catch {}
|
|
try {
|
|
execFileSync(
|
|
'cast',
|
|
[
|
|
'call',
|
|
integrationAddress,
|
|
'swapExactIn(address,address,uint256,uint256)(uint256)',
|
|
poolAddress,
|
|
String(parseCastScalar(baseToken)),
|
|
String(probeAmount),
|
|
'1',
|
|
'--from',
|
|
traderAddress,
|
|
'--rpc-url',
|
|
rpcUrl,
|
|
],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
integrationSellBaseOk = true
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
const hasReserveSurface = tryReadPoolReservesViaCast(chainId, poolAddress) != null
|
|
const hasMidPriceSurface = midPrice != null
|
|
const quotePushOk = sellQuoteOk || integrationSellQuoteOk
|
|
const baseUnwindOk = sellBaseOk || integrationSellBaseOk
|
|
let surface = 'unknown'
|
|
if (quotePushOk && baseUnwindOk) {
|
|
surface = 'full_quote_surface'
|
|
} else if (quotePushOk || baseUnwindOk) {
|
|
surface = 'strategy_specific_quote_surface'
|
|
} else if (hasReserveSurface && hasMidPriceSurface) {
|
|
surface = 'partial_dodo_surface_integration_only'
|
|
} else if (hasReserveSurface) {
|
|
surface = 'reserve_only'
|
|
}
|
|
|
|
return {
|
|
surface,
|
|
sellBaseOk,
|
|
sellQuoteOk,
|
|
integrationSellBaseOk,
|
|
integrationSellQuoteOk,
|
|
quotePushOk,
|
|
baseUnwindOk,
|
|
hasReserveSurface,
|
|
hasMidPriceSurface,
|
|
baseToken: baseToken ? String(parseCastScalar(baseToken)) : null,
|
|
quoteToken: quoteToken ? String(parseCastScalar(quoteToken)) : null,
|
|
source: `live pool probe via chain ${chainId} RPC`,
|
|
}
|
|
}
|
|
|
|
function extractFirstNumericToken(raw) {
|
|
if (!raw) return null
|
|
const match = String(raw).match(/-?\d+(?:\.\d+)?(?:e[+-]?\d+)?/i)
|
|
if (!match) return null
|
|
const value = Number(match[0])
|
|
return Number.isFinite(value) ? value : null
|
|
}
|
|
|
|
function tryReadNumericCommand(command, sourceLabel) {
|
|
if (!command) return null
|
|
try {
|
|
const raw = execFileSync('bash', ['-lc', command], {
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
}).trim()
|
|
const value = extractFirstNumericToken(raw)
|
|
if (!(value > 0)) return null
|
|
return {
|
|
value,
|
|
source: sourceLabel ?? `live command: ${command}`,
|
|
raw,
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryEstimateIntegrationSwapGasViaCast({
|
|
integrationAddress,
|
|
poolAddress,
|
|
tokenIn,
|
|
amountIn,
|
|
minOut,
|
|
fromAddress,
|
|
rpcUrl,
|
|
}) {
|
|
if (!integrationAddress || !poolAddress || !tokenIn || !fromAddress || !rpcUrl || !(amountIn > 0)) {
|
|
return null
|
|
}
|
|
try {
|
|
const raw = execFileSync(
|
|
'cast',
|
|
[
|
|
'estimate',
|
|
'--from',
|
|
fromAddress,
|
|
integrationAddress,
|
|
'swapExactIn(address,address,uint256,uint256)(uint256)',
|
|
poolAddress,
|
|
tokenIn,
|
|
String(amountIn),
|
|
String(Math.max(1, minOut ?? 1)),
|
|
'--rpc-url',
|
|
rpcUrl,
|
|
],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
const gasUnits = Number(parseCastScalar(raw))
|
|
return Number.isFinite(gasUnits) ? gasUnits : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryProbeErc3156FlashProvider(providerAddress, tokenAddress, amount, rpcUrl) {
|
|
if (!providerAddress || !tokenAddress || !(amount >= 0) || !rpcUrl) return null
|
|
try {
|
|
const maxLoanRaw = execFileSync(
|
|
'cast',
|
|
['call', providerAddress, 'maxFlashLoan(address)(uint256)', tokenAddress, '--rpc-url', rpcUrl],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
const feeRaw = execFileSync(
|
|
'cast',
|
|
['call', providerAddress, 'flashFee(address,uint256)(uint256)', tokenAddress, String(amount), '--rpc-url', rpcUrl],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
)
|
|
const maxFlashLoan = Number(parseCastScalar(maxLoanRaw))
|
|
const flashFeeAmount = Number(parseCastScalar(feeRaw))
|
|
if (!Number.isFinite(maxFlashLoan) || !Number.isFinite(flashFeeAmount)) return null
|
|
return {
|
|
maxFlashLoan,
|
|
flashFeeAmount,
|
|
source: `live ERC-3156 probe via ${providerAddress}`,
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function tryReadMainnetBalancerFlashSource(tokenAddress) {
|
|
const rpcUrl = rpcUrlForChain(1)
|
|
if (!rpcUrl || !tokenAddress) return null
|
|
const code = tryReadCastCode(DEFAULT_MAINNET_BALANCER_VAULT, rpcUrl)
|
|
if (!code || code === '0x') return null
|
|
const collector = tryCastCallRaw(
|
|
DEFAULT_MAINNET_BALANCER_VAULT,
|
|
'getProtocolFeesCollector()(address)',
|
|
[],
|
|
rpcUrl,
|
|
)
|
|
const feeRaw = collector
|
|
? tryCastCallRaw(collector, 'getFlashLoanFeePercentage()(uint256)', [], rpcUrl)
|
|
: null
|
|
const feeScalar = feeRaw != null ? Number(parseCastScalar(feeRaw)) : NaN
|
|
const feeBps = Number.isFinite(feeScalar) ? feeScalar / 1e14 : 0
|
|
const capRaw = tryReadTokenRawBalanceViaCast(rpcUrl, tokenAddress, DEFAULT_MAINNET_BALANCER_VAULT)
|
|
return {
|
|
providerKey: 'balancer',
|
|
providerName: 'Balancer mainnet vault',
|
|
providerAddress: DEFAULT_MAINNET_BALANCER_VAULT,
|
|
capRaw,
|
|
feeBps,
|
|
feeRaw: feeRaw != null ? parseCastScalar(feeRaw) : null,
|
|
source: 'live Balancer mainnet vault probe',
|
|
}
|
|
}
|
|
|
|
function tryReadMainnetAaveFlashSource(tokenAddress) {
|
|
const rpcUrl = rpcUrlForChain(1)
|
|
if (!rpcUrl || !tokenAddress) return null
|
|
const code = tryReadCastCode(DEFAULT_MAINNET_AAVE_V3_POOL, rpcUrl)
|
|
if (!code || code === '0x') return null
|
|
const provider = tryCastCallRaw(
|
|
DEFAULT_MAINNET_AAVE_V3_POOL,
|
|
'ADDRESSES_PROVIDER()(address)',
|
|
[],
|
|
rpcUrl,
|
|
)
|
|
const feeRaw = tryCastCallRaw(
|
|
DEFAULT_MAINNET_AAVE_V3_POOL,
|
|
'FLASHLOAN_PREMIUM_TOTAL()(uint128)',
|
|
[],
|
|
rpcUrl,
|
|
)
|
|
const feeBps = feeRaw != null ? Number(parseCastScalar(feeRaw)) : NaN
|
|
const reserveData = tryCastCallRaw(
|
|
DEFAULT_MAINNET_AAVE_V3_POOL,
|
|
'getReserveData(address)((uint256,uint128,uint128,uint128,uint128,uint128,uint40,address,address,address,address,uint8))',
|
|
[tokenAddress],
|
|
rpcUrl,
|
|
)
|
|
const aTokenAddress = extractNthAddress(reserveData, 2)
|
|
const capRaw =
|
|
aTokenAddress != null ? tryReadTokenRawBalanceViaCast(rpcUrl, tokenAddress, aTokenAddress) : null
|
|
return {
|
|
providerKey: 'aave',
|
|
providerName: 'Aave V3 mainnet',
|
|
providerAddress: DEFAULT_MAINNET_AAVE_V3_POOL,
|
|
providerAuxAddress: provider ?? DEFAULT_MAINNET_AAVE_V3_PROVIDER,
|
|
capRaw,
|
|
feeBps: Number.isFinite(feeBps) ? feeBps : null,
|
|
feeRaw: feeRaw != null ? parseCastScalar(feeRaw) : null,
|
|
source: 'live Aave V3 mainnet probe',
|
|
}
|
|
}
|
|
|
|
function tryEstimateMainnetIntegrationGasQuote({
|
|
poolAddress,
|
|
tokenIn,
|
|
amountIn,
|
|
quoteDecimals,
|
|
nativeTokenPrice,
|
|
fromAddress,
|
|
}) {
|
|
const rpcUrl = rpcUrlForChain(1)
|
|
const integrationAddress = process.env.DODO_PMM_INTEGRATION_MAINNET ?? null
|
|
if (
|
|
!rpcUrl ||
|
|
!integrationAddress ||
|
|
!poolAddress ||
|
|
!tokenIn ||
|
|
!(amountIn > 0) ||
|
|
!(quoteDecimals >= 0) ||
|
|
!(nativeTokenPrice > 0) ||
|
|
!fromAddress
|
|
) {
|
|
return null
|
|
}
|
|
const gasUnits = tryEstimateIntegrationSwapGasViaCast({
|
|
integrationAddress,
|
|
poolAddress,
|
|
tokenIn,
|
|
amountIn,
|
|
minOut: 1,
|
|
fromAddress,
|
|
rpcUrl,
|
|
})
|
|
if (!(gasUnits > 0)) return null
|
|
let gasPriceWei = null
|
|
try {
|
|
const raw = execFileSync('cast', ['gas-price', '--rpc-url', rpcUrl], {
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
})
|
|
gasPriceWei = Number(parseCastScalar(raw))
|
|
} catch {}
|
|
if (!(gasPriceWei > 0)) return null
|
|
const nativeCost = gasUnits * gasPriceWei * 1e-18
|
|
const quoteCost = nativeCost * nativeTokenPrice
|
|
if (!(quoteCost > 0)) return null
|
|
return {
|
|
gasUnits,
|
|
gasPriceWei,
|
|
quoteCost,
|
|
source: `live cast estimate on DODOPMMIntegration mainnet swap path (${gasUnits} gas @ ${fmt(gasPriceWei)} wei)`,
|
|
}
|
|
}
|
|
|
|
function resolveTokenBalance({
|
|
chainId,
|
|
symbol,
|
|
tokenAddress,
|
|
report,
|
|
reportPath,
|
|
deployerAddress,
|
|
}) {
|
|
const live = tokenAddress && deployerAddress ? tryReadTokenBalanceViaCast(chainId, tokenAddress, deployerAddress) : null
|
|
if (live) return live
|
|
const reportBalance = sumReportBalance(report, chainId, symbol)
|
|
const reportInfo = sumReportBalanceInfo(report, chainId, symbol)
|
|
if (reportInfo) {
|
|
return {
|
|
...reportInfo,
|
|
source: reportPath ? `audit report ${reportPath}` : 'audit report',
|
|
}
|
|
}
|
|
if (reportBalance != null) {
|
|
return {
|
|
balance: reportBalance,
|
|
source: reportPath ? `audit report ${reportPath}` : 'audit report',
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
function repayUsdc(grossQuoteIn, flashFeeBps) {
|
|
const bps = Math.max(0, flashFeeBps)
|
|
return grossQuoteIn * (1 + bps / 10000)
|
|
}
|
|
|
|
function repayUsdcWithExactFee(grossQuoteIn, flashFeeAmount, flashFeeBps) {
|
|
if (flashFeeAmount != null) return grossQuoteIn + flashFeeAmount
|
|
return repayUsdc(grossQuoteIn, flashFeeBps)
|
|
}
|
|
|
|
/** Net USDC per base after external sale (fold slippage + fees into P_eff) */
|
|
function effectiveExternalPrice(externalPrice, exitFeeBps) {
|
|
const bps = Math.min(10000, Math.max(0, exitFeeBps))
|
|
return externalPrice * ((10000 - bps) / 10000)
|
|
}
|
|
|
|
function effectiveExternalEntryPrice(externalPrice, entryFeeBps) {
|
|
const bps = Math.min(10000, Math.max(0, entryFeeBps))
|
|
return externalPrice * ((10000 + bps) / 10000)
|
|
}
|
|
|
|
function externalQuoteToBaseOut(grossQuoteIn, externalPrice, entryFeeBps, quoteDecimals, baseDecimals) {
|
|
const pEff = effectiveExternalEntryPrice(externalPrice, entryFeeBps)
|
|
const quoteInHuman = rawToHuman(grossQuoteIn, quoteDecimals)
|
|
const baseOutHuman = pEff > 0 ? quoteInHuman / pEff : 0
|
|
const baseOut = humanToRaw(baseOutHuman, baseDecimals)
|
|
return { effectivePrice: pEff, baseOut }
|
|
}
|
|
|
|
function externalBaseToQuoteOut(baseIn, externalPrice, exitFeeBps, baseDecimals, quoteDecimals) {
|
|
const pEff = effectiveExternalPrice(externalPrice, exitFeeBps)
|
|
const baseInHuman = rawToHuman(baseIn, baseDecimals)
|
|
const quoteOutHuman = baseInHuman * pEff
|
|
const quoteOut = humanToRaw(quoteOutHuman, quoteDecimals)
|
|
return { effectivePrice: pEff, quoteOut }
|
|
}
|
|
|
|
function formatExternalRoute(fromAsset, toAsset, viaAsset) {
|
|
return viaAsset ? `${fromAsset} -> ${viaAsset} -> ${toAsset}` : `${fromAsset} -> ${toAsset}`
|
|
}
|
|
|
|
function profit(grossQuoteIn, baseOut, externalPrice, flashFeeBps, exitFeeBps) {
|
|
const pEff = effectiveExternalPrice(externalPrice, exitFeeBps)
|
|
const proceeds = pEff * baseOut
|
|
const repay = repayUsdc(grossQuoteIn, flashFeeBps)
|
|
return proceeds - repay
|
|
}
|
|
|
|
function breakEvenExternalPrice(grossQuoteIn, baseOut, flashFeeBps, exitFeeBps) {
|
|
if (baseOut <= 0) return Infinity
|
|
const repay = repayUsdc(grossQuoteIn, flashFeeBps)
|
|
const exitMult = (10000 - Math.min(10000, Math.max(0, exitFeeBps))) / 10000
|
|
if (exitMult <= 0) return Infinity
|
|
return repay / (baseOut * exitMult)
|
|
}
|
|
|
|
function fmt(n) {
|
|
if (!Number.isFinite(n)) return String(n)
|
|
if (Math.abs(n) >= 1e9) return n.toExponential(4)
|
|
return n.toLocaleString('en-US', { maximumFractionDigits: 6 })
|
|
}
|
|
|
|
function rawToHuman(raw, decimals) {
|
|
if (!Number.isFinite(raw) || !Number.isFinite(decimals)) return NaN
|
|
return raw / 10 ** decimals
|
|
}
|
|
|
|
function humanToRaw(amount, decimals) {
|
|
if (!Number.isFinite(amount) || !Number.isFinite(decimals)) return NaN
|
|
return amount * 10 ** decimals
|
|
}
|
|
|
|
function convertRawAmount(raw, fromDecimals, toDecimals) {
|
|
if (!Number.isFinite(raw) || !Number.isFinite(fromDecimals) || !Number.isFinite(toDecimals)) {
|
|
return NaN
|
|
}
|
|
return humanToRaw(rawToHuman(raw, fromDecimals), toDecimals)
|
|
}
|
|
|
|
function fmtAmount(raw, decimals, symbol) {
|
|
if (!Number.isFinite(raw) || !Number.isFinite(decimals)) {
|
|
return symbol ? `${fmt(raw)} ${symbol}` : fmt(raw)
|
|
}
|
|
const human = rawToHuman(raw, decimals)
|
|
const humanLabel = symbol ? `${fmt(human)} ${symbol}` : fmt(human)
|
|
return `${humanLabel} (raw ${fmt(raw)})`
|
|
}
|
|
|
|
function approxBpsFromAmount(amount, feeAmount) {
|
|
if (!(amount > 0) || !Number.isFinite(feeAmount)) return NaN
|
|
return (feeAmount / amount) * 10000
|
|
}
|
|
|
|
function parseNum(s) {
|
|
const n = Number(s)
|
|
if (!Number.isFinite(n)) throw new Error(`Invalid number: ${s}`)
|
|
return n
|
|
}
|
|
|
|
function parsePositiveInt(s, label) {
|
|
const n = parseNum(s)
|
|
if (!Number.isInteger(n) || n <= 0) {
|
|
throw new Error(`${label} must be a positive integer`)
|
|
}
|
|
return n
|
|
}
|
|
|
|
function parseNonNegativeInt(s, label) {
|
|
const n = parseNum(s)
|
|
if (!Number.isInteger(n) || n < 0) {
|
|
throw new Error(`${label} must be a non-negative integer`)
|
|
}
|
|
return n
|
|
}
|
|
|
|
function marginalQuotePerBase(baseReserve, quoteReserve) {
|
|
if (!(baseReserve > 0) || !(quoteReserve >= 0)) return NaN
|
|
return quoteReserve / baseReserve
|
|
}
|
|
|
|
function deviationBps(price, oraclePrice) {
|
|
if (!(price >= 0) || !(oraclePrice > 0)) return NaN
|
|
return ((price - oraclePrice) / oraclePrice) * 10000
|
|
}
|
|
|
|
function calcNativeGasReserve(gasTxCount, gasPerTx, maxFeeGwei) {
|
|
if (!(gasTxCount > 0) || !(gasPerTx > 0) || !(maxFeeGwei > 0)) return 0
|
|
return gasTxCount * gasPerTx * maxFeeGwei * 1e-9
|
|
}
|
|
|
|
function buildInventoryLoopGuardConfig({
|
|
minRetainedUsdc,
|
|
gasTxCount,
|
|
gasPerTx,
|
|
maxFeeGwei,
|
|
nativeTokenPrice,
|
|
quoteDecimals,
|
|
oraclePrice,
|
|
maxPostTradeDeviationBps,
|
|
maxCwBurnFraction,
|
|
cooldownBlocks,
|
|
measuredGasQuote,
|
|
measuredGasSource,
|
|
estimatedGasQuote,
|
|
estimatedGasSource,
|
|
}) {
|
|
const measuredGasQuoteRaw =
|
|
measuredGasQuote != null && measuredGasQuote > 0 ? humanToRaw(measuredGasQuote, quoteDecimals) : 0
|
|
const estimatedGasQuoteRaw =
|
|
estimatedGasQuote != null && estimatedGasQuote > 0 ? humanToRaw(estimatedGasQuote, quoteDecimals) : 0
|
|
const gasReserveNative =
|
|
gasTxCount != null && gasPerTx != null && maxFeeGwei != null
|
|
? calcNativeGasReserve(gasTxCount, gasPerTx, maxFeeGwei)
|
|
: 0
|
|
const modeledGasReserveQuoteHuman =
|
|
gasReserveNative > 0 && nativeTokenPrice != null ? gasReserveNative * nativeTokenPrice : 0
|
|
const gasReserveQuoteRaw =
|
|
measuredGasQuoteRaw > 0
|
|
? measuredGasQuoteRaw
|
|
: estimatedGasQuoteRaw > 0
|
|
? estimatedGasQuoteRaw
|
|
: modeledGasReserveQuoteHuman > 0
|
|
? humanToRaw(modeledGasReserveQuoteHuman, quoteDecimals)
|
|
: 0
|
|
const gasReserveQuoteHuman =
|
|
measuredGasQuoteRaw > 0
|
|
? measuredGasQuote
|
|
: estimatedGasQuoteRaw > 0
|
|
? estimatedGasQuote
|
|
: modeledGasReserveQuoteHuman
|
|
return {
|
|
minRetainedUsdc,
|
|
gasTxCount,
|
|
gasPerTx,
|
|
maxFeeGwei,
|
|
nativeTokenPrice,
|
|
quoteDecimals,
|
|
gasReserveNative,
|
|
gasReserveQuoteHuman,
|
|
gasReserveQuote: gasReserveQuoteRaw,
|
|
gasReserveMode:
|
|
measuredGasQuoteRaw > 0
|
|
? 'measured_quote'
|
|
: estimatedGasQuoteRaw > 0
|
|
? 'estimated_quote'
|
|
: 'modeled_native',
|
|
gasReserveSource:
|
|
measuredGasQuoteRaw > 0
|
|
? measuredGasSource ?? 'CLI measured gas quote override'
|
|
: estimatedGasQuoteRaw > 0
|
|
? estimatedGasSource ?? 'live integration gas estimate'
|
|
: 'modeled from gas policy inputs',
|
|
requiredRetainedUsdc: minRetainedUsdc + gasReserveQuoteRaw,
|
|
oraclePrice,
|
|
maxPostTradeDeviationBps,
|
|
maxCwBurnFraction,
|
|
cooldownBlocks,
|
|
}
|
|
}
|
|
|
|
function summarizeGasReserve(guardConfig) {
|
|
if (!(guardConfig.gasReserveQuote > 0)) return null
|
|
if (guardConfig.gasReserveMode === 'measured_quote') {
|
|
return `${fmt(guardConfig.gasReserveQuoteHuman ?? rawToHuman(guardConfig.gasReserveQuote, guardConfig.quoteDecimals))} quote units from ${guardConfig.gasReserveSource}`
|
|
}
|
|
if (guardConfig.gasReserveMode === 'estimated_quote') {
|
|
return `${fmt(guardConfig.gasReserveQuoteHuman ?? rawToHuman(guardConfig.gasReserveQuote, guardConfig.quoteDecimals))} quote units from ${guardConfig.gasReserveSource}`
|
|
}
|
|
return `${guardConfig.gasTxCount} tx * ${fmt(guardConfig.gasPerTx)} gas * ${fmt(guardConfig.maxFeeGwei)} gwei @ native price ${fmt(guardConfig.nativeTokenPrice)}`
|
|
}
|
|
|
|
function classifyExecutionReadiness({
|
|
strategyLabel,
|
|
quoteSurface,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
gasReserveMode,
|
|
maxPostTradeDeviationBps,
|
|
}) {
|
|
const missing = listExecutionReadinessBlocks({
|
|
strategyLabel,
|
|
quoteSurface,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
gasReserveMode,
|
|
maxPostTradeDeviationBps,
|
|
})
|
|
if (missing.length === 0) return 'execution-ready'
|
|
if (missing.some((item) => item.includes('live pool quote surface'))) return 'blocked'
|
|
return 'planning'
|
|
}
|
|
|
|
function hasRequiredLivePoolQuoteSurface(strategyLabel, quoteSurface) {
|
|
if (!quoteSurface) return false
|
|
if (typeof quoteSurface === 'string') return quoteSurface === 'full_quote_surface'
|
|
if (quoteSurface.surface === 'full_quote_surface') return true
|
|
if (strategyLabel === 'quote-push') return quoteSurface.quotePushOk === true
|
|
if (strategyLabel === 'base-unwind') return quoteSurface.baseUnwindOk === true
|
|
if (strategyLabel === 'both') {
|
|
return quoteSurface.quotePushOk === true && quoteSurface.baseUnwindOk === true
|
|
}
|
|
return false
|
|
}
|
|
|
|
function listExecutionReadinessBlocks({
|
|
strategyLabel,
|
|
quoteSurface,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
gasReserveMode,
|
|
maxPostTradeDeviationBps,
|
|
}) {
|
|
const hasLivePoolQuote = hasRequiredLivePoolQuoteSurface(strategyLabel, quoteSurface)
|
|
const needsLiveEntry = strategyLabel === 'base-unwind' || strategyLabel === 'both'
|
|
const needsLiveExit = strategyLabel === 'quote-push' || strategyLabel === 'both'
|
|
const hasLiveExternalEntry =
|
|
!needsLiveEntry || externalEntrySource == null || externalEntrySource === 'live'
|
|
const hasLiveExternalExit =
|
|
!needsLiveExit || externalExitSource == null || externalExitSource === 'live'
|
|
const hasMeasuredGas = gasReserveMode === 'measured_quote' || gasReserveMode === 'estimated_quote'
|
|
const hasExplicitDeviationCap = maxPostTradeDeviationBps != null
|
|
|
|
const missing = []
|
|
if (!hasLivePoolQuote) {
|
|
missing.push(
|
|
strategyLabel === 'both'
|
|
? 'full live pool quote surface'
|
|
: `live pool quote surface for ${strategyLabel}`,
|
|
)
|
|
}
|
|
if (!hasLiveExternalEntry) missing.push('live external entry quote source')
|
|
if (!hasLiveExternalExit) missing.push('live external exit quote source')
|
|
if (!hasMeasuredGas) missing.push('measured or live-estimated gas')
|
|
if (!hasExplicitDeviationCap) missing.push('explicit post-trade deviation cap')
|
|
return missing
|
|
}
|
|
|
|
function formatGuardStatus(candidate) {
|
|
return candidate.recommendationOk ? 'pass' : candidate.guardFailures.join('+')
|
|
}
|
|
|
|
function cmpAsc(a, b) {
|
|
return a - b
|
|
}
|
|
|
|
function cmpDesc(a, b) {
|
|
return b - a
|
|
}
|
|
|
|
function compareRankProfileMetrics(a, b, metrics) {
|
|
for (const metric of metrics) {
|
|
const av = metric.pick(a)
|
|
const bv = metric.pick(b)
|
|
if (av !== bv) return metric.order(av, bv)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
function eligibleRankMetrics(targetFlashBorrow, rankProfile) {
|
|
const efficiencyMetric =
|
|
targetFlashBorrow != null
|
|
? (candidate) => candidate.retainedAfterTargetFlashPerCw
|
|
: (candidate) => candidate.retainedPerCw
|
|
|
|
switch (rankProfile) {
|
|
case 'conservative':
|
|
return [
|
|
{ pick: (candidate) => candidate.postTradeDeviationAbsBps, order: cmpAsc },
|
|
{ pick: (candidate) => candidate.inventoryBurnFraction ?? Infinity, order: cmpAsc },
|
|
{ pick: (candidate) => candidate.grossBaseIn, order: cmpAsc },
|
|
...(targetFlashBorrow != null
|
|
? [
|
|
{ pick: (candidate) => candidate.floorMarginAfterTargetFlash, order: cmpDesc },
|
|
{ pick: (candidate) => candidate.repayMargin, order: cmpDesc },
|
|
]
|
|
: [
|
|
{ pick: (candidate) => candidate.maxFlashKeepFloor, order: cmpDesc },
|
|
{ pick: (candidate) => candidate.floorSurplusNoFlash, order: cmpDesc },
|
|
]),
|
|
{ pick: efficiencyMetric, order: cmpDesc },
|
|
]
|
|
case 'efficiency':
|
|
return [
|
|
{ pick: efficiencyMetric, order: cmpDesc },
|
|
...(targetFlashBorrow != null
|
|
? [
|
|
{ pick: (candidate) => candidate.floorMarginAfterTargetFlash, order: cmpDesc },
|
|
{ pick: (candidate) => candidate.repayMargin, order: cmpDesc },
|
|
]
|
|
: [
|
|
{ pick: (candidate) => candidate.maxFlashKeepFloor, order: cmpDesc },
|
|
{ pick: (candidate) => candidate.floorSurplusNoFlash, order: cmpDesc },
|
|
]),
|
|
{ pick: (candidate) => candidate.postTradeDeviationAbsBps, order: cmpAsc },
|
|
{ pick: (candidate) => candidate.inventoryBurnFraction ?? Infinity, order: cmpAsc },
|
|
{ pick: (candidate) => candidate.grossBaseIn, order: cmpAsc },
|
|
]
|
|
case 'balanced':
|
|
default:
|
|
return [
|
|
...(targetFlashBorrow != null
|
|
? [
|
|
{ pick: (candidate) => candidate.repayMargin, order: cmpDesc },
|
|
{ pick: (candidate) => candidate.floorMarginAfterTargetFlash, order: cmpDesc },
|
|
]
|
|
: [
|
|
{ pick: (candidate) => candidate.maxFlashKeepFloor, order: cmpDesc },
|
|
{ pick: (candidate) => candidate.floorSurplusNoFlash, order: cmpDesc },
|
|
]),
|
|
{ pick: efficiencyMetric, order: cmpDesc },
|
|
{ pick: (candidate) => candidate.postTradeDeviationAbsBps, order: cmpAsc },
|
|
{ pick: (candidate) => candidate.inventoryBurnFraction ?? Infinity, order: cmpAsc },
|
|
{ pick: (candidate) => candidate.grossBaseIn, order: cmpAsc },
|
|
]
|
|
}
|
|
}
|
|
|
|
function efficiencyMetricLabel(targetFlashBorrow) {
|
|
return targetFlashBorrow != null ? 'retained_after_repay_per_cw' : 'retained_per_cw'
|
|
}
|
|
|
|
function candidateEfficiencyValue(candidate, targetFlashBorrow) {
|
|
return targetFlashBorrow != null ? candidate.retainedAfterTargetFlashPerCw : candidate.retainedPerCw
|
|
}
|
|
|
|
function rankProfileSummary(rankProfile, targetFlashBorrow) {
|
|
switch (rankProfile) {
|
|
case 'conservative':
|
|
return targetFlashBorrow != null
|
|
? 'lowest post-trade deviation, lowest cW burn, then stronger post-repay safety headroom'
|
|
: 'lowest post-trade deviation, lowest cW burn, then stronger retained-floor headroom'
|
|
case 'efficiency':
|
|
return targetFlashBorrow != null
|
|
? 'best retained USDC after repay per cW, then safety headroom, then lower impact'
|
|
: 'best retained USDC per cW, then retained-floor headroom, then lower impact'
|
|
case 'balanced':
|
|
default:
|
|
return targetFlashBorrow != null
|
|
? 'strongest repay margin and post-repay floor headroom, then better efficiency and lower impact'
|
|
: 'largest flash/floor headroom, then better efficiency and lower impact'
|
|
}
|
|
}
|
|
|
|
function explainWinningCandidate(recommended, runnerUp, rankProfile, targetFlashBorrow) {
|
|
if (!recommended) return null
|
|
const efficiencyLabel = efficiencyMetricLabel(targetFlashBorrow)
|
|
const recommendedEfficiency = fmt(candidateEfficiencyValue(recommended, targetFlashBorrow))
|
|
switch (rankProfile) {
|
|
case 'conservative':
|
|
if (runnerUp) {
|
|
return `Won on lower post-trade deviation (${fmt(recommended.postTradeDeviationAbsBps)} vs ${fmt(runnerUp.postTradeDeviationAbsBps)} bps) and lower cW burn (${fmt(recommended.inventoryBurnFraction)} vs ${fmt(runnerUp.inventoryBurnFraction)}).`
|
|
}
|
|
return `Won by minimizing post-trade deviation (${fmt(recommended.postTradeDeviationAbsBps)} bps) and cW burn (${fmt(recommended.inventoryBurnFraction)}).`
|
|
case 'efficiency':
|
|
if (runnerUp) {
|
|
return `Won on better ${efficiencyLabel} (${recommendedEfficiency} vs ${fmt(candidateEfficiencyValue(runnerUp, targetFlashBorrow))}) while still passing the active guard set.`
|
|
}
|
|
return `Won on best ${efficiencyLabel} (${recommendedEfficiency}) among the eligible set.`
|
|
case 'balanced':
|
|
default:
|
|
if (targetFlashBorrow != null) {
|
|
if (runnerUp) {
|
|
return `Won on stronger repay margin (${fmt(recommended.repayMargin)} vs ${fmt(runnerUp.repayMargin)}) and post-repay floor margin (${fmt(recommended.floorMarginAfterTargetFlash)} vs ${fmt(runnerUp.floorMarginAfterTargetFlash)}).`
|
|
}
|
|
return `Won on strongest repay margin (${fmt(recommended.repayMargin)}) and post-repay floor margin (${fmt(recommended.floorMarginAfterTargetFlash)}).`
|
|
}
|
|
if (runnerUp) {
|
|
return `Won on higher max flash-with-floor headroom (${fmt(recommended.maxFlashKeepFloor)} vs ${fmt(runnerUp.maxFlashKeepFloor)}) and higher floor surplus (${fmt(recommended.floorSurplusNoFlash)} vs ${fmt(runnerUp.floorSurplusNoFlash)}).`
|
|
}
|
|
return `Won on highest flash-with-floor headroom (${fmt(recommended.maxFlashKeepFloor)}) and floor surplus (${fmt(recommended.floorSurplusNoFlash)}).`
|
|
}
|
|
}
|
|
|
|
function printScenario(label, B, Q, x, lpFeeBps, flashFeeBps, externalPrice, exitFeeBps) {
|
|
const { deltaXNet, baseOut, vwap } = quoteInToBaseOut(B, Q, x, lpFeeBps)
|
|
const repay = repayUsdc(x, flashFeeBps)
|
|
const pStar = breakEvenExternalPrice(x, baseOut, flashFeeBps, exitFeeBps)
|
|
const pi =
|
|
externalPrice != null && Number.isFinite(externalPrice)
|
|
? profit(x, baseOut, externalPrice, flashFeeBps, exitFeeBps)
|
|
: null
|
|
|
|
console.log(`--- ${label} ---`)
|
|
console.log(` baseReserve B=${fmt(B)} quoteReserve Q=${fmt(Q)} gross quote in x=${fmt(x)}`)
|
|
console.log(` lpFeeBps=${lpFeeBps} flashFeeBps=${flashFeeBps} exitFeeBps=${exitFeeBps}`)
|
|
console.log(` Δx_net (after LP fee)=${fmt(deltaXNet)}`)
|
|
console.log(` base out Δy=${fmt(baseOut)}`)
|
|
console.log(` VWAP (gross quote / base)=${fmt(vwap)}`)
|
|
console.log(` repay (x * (1+flash))=${fmt(repay)}`)
|
|
console.log(` break-even P_ext*=${fmt(pStar)} quote per base`)
|
|
if (pi != null) console.log(` PnL @ externalPrice=${externalPrice} (after exit fee)=${fmt(pi)}`)
|
|
console.log('')
|
|
}
|
|
|
|
function printMainnetMap(
|
|
compareDeepenDelta,
|
|
B,
|
|
Q,
|
|
scanStr,
|
|
lpFeeBps,
|
|
registryFeeBps,
|
|
preferRegistryFeeForDeepen,
|
|
) {
|
|
const m = loadMainnetCwUsdcUsdc()
|
|
console.log('Ethereum Mainnet — cWUSDC / USDC PMM mapping (repo registry)\n')
|
|
if (!m) {
|
|
console.log(' (Could not read cross-chain-pmm-lps/config/deployment-status.json)\n')
|
|
return
|
|
}
|
|
const feeForTables =
|
|
compareDeepenDelta != null && preferRegistryFeeForDeepen && registryFeeBps != null
|
|
? registryFeeBps
|
|
: lpFeeBps
|
|
const feeSource =
|
|
compareDeepenDelta != null && preferRegistryFeeForDeepen && registryFeeBps != null
|
|
? 'registry row fee (default for --compare-deepen)'
|
|
: '--lp-fee-bps'
|
|
console.log(' Token cWUSDC (ops shorthand xWUSDC):', m.cWUSDC)
|
|
console.log(' Token USDC (anchor): ', m.USDC)
|
|
console.log(' DODO PMM pool: ', m.poolAddress)
|
|
console.log(' Registered LP fee: ', m.feeBps, 'bps')
|
|
console.log('')
|
|
console.log(' Loop (funded USDC inventory, not necessarily flash credit):')
|
|
console.log(' USDC → ETH (Uniswap / deep CLOB): charge slippage + pool fee into leg-1')
|
|
console.log(' ETH → cWUSDC: use any liquid route; if none, extra hop(s) — fold into acquisition cost')
|
|
console.log(' cWUSDC → USDC on this PMM: sell-base formula')
|
|
console.log(' quoteOut = netBaseIn * Q / (B + netBaseIn)')
|
|
console.log('')
|
|
console.log(' For acquisition via PMM (USDC → cWUSDC) use quote-in branch (this script default):')
|
|
console.log(' baseOut = netQuoteIn * B / (Q + netQuoteIn)')
|
|
console.log('')
|
|
console.log(' Model Aave-style flash: keep --flash-fee-bps 5; funded pool loop: often --flash-fee-bps 0.')
|
|
console.log('')
|
|
|
|
if (compareDeepenDelta != null && Number.isFinite(compareDeepenDelta) && compareDeepenDelta > 0) {
|
|
if (B == null || Q == null) {
|
|
console.log(' --compare-deepen requires -B and -Q (live reserves from pool getVaultReserve).')
|
|
return
|
|
}
|
|
const sizes = scanStr
|
|
? scanStr.split(',').map((s) => parseNum(s.trim()))
|
|
: [500_000, 1_000_000, 2_000_000, 5_000_000]
|
|
const B2 = B + compareDeepenDelta
|
|
console.log(
|
|
` Depth add: +${fmt(compareDeepenDelta)} cWUSDC to base → B: ${fmt(B)} → ${fmt(B2)} (Q fixed ${fmt(Q)})`,
|
|
)
|
|
console.log(` Table LP fee: ${feeForTables} bps (${feeSource})`)
|
|
console.log('')
|
|
console.log(' USDC→cWUSDC (quote in): trade_x | baseOut_before | VWAP_before | baseOut_after | VWAP_after')
|
|
for (const x of sizes) {
|
|
const a = quoteInToBaseOut(B, Q, x, feeForTables)
|
|
const b = quoteInToBaseOut(B2, Q, x, feeForTables)
|
|
console.log(
|
|
[fmt(x), fmt(a.baseOut), fmt(a.vwap), fmt(b.baseOut), fmt(b.vwap)].join('\t'),
|
|
)
|
|
}
|
|
console.log('')
|
|
console.log(' cWUSDC→USDC (base in, same sizes as cWUSDC sold): | quoteOut_before | USDC/base | quoteOut_after | USDC/base')
|
|
for (const x of sizes) {
|
|
const a = baseInToQuoteOut(B, Q, x, feeForTables)
|
|
const b = baseInToQuoteOut(B2, Q, x, feeForTables)
|
|
console.log(
|
|
[fmt(x), fmt(a.quoteOut), fmt(a.vwapQuotePerBase), fmt(b.quoteOut), fmt(b.vwapQuotePerBase)].join(
|
|
'\t',
|
|
),
|
|
)
|
|
}
|
|
console.log('')
|
|
}
|
|
}
|
|
|
|
function printInventoryCoverage(inventoryBase, requiredBase, label = 'Required cW base inventory') {
|
|
if (inventoryBase == null) return
|
|
const remaining = inventoryBase - requiredBase
|
|
console.log(` inventoryBase available=${fmt(inventoryBase)}`)
|
|
console.log(` ${label}=${fmt(requiredBase)}`)
|
|
console.log(
|
|
` inventory coverage=${remaining >= 0 ? 'enough' : 'short'} (${remaining >= 0 ? 'remaining' : 'shortfall'} ${fmt(Math.abs(remaining))})`,
|
|
)
|
|
}
|
|
|
|
function printSeedingPlanFromPmm(
|
|
B,
|
|
Q,
|
|
totalQuoteTarget,
|
|
lpFeeBps,
|
|
inventoryBase,
|
|
loopCount,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
) {
|
|
console.log('Quote seeding plan — source PMM sell-base path\n')
|
|
console.log(` source reserves: B=${fmt(B)} cW base, Q=${fmt(Q)} USDC quote`)
|
|
console.log(` pool fee=${lpFeeBps} bps`)
|
|
console.log(` target USDC to raise=${fmt(totalQuoteTarget)}`)
|
|
if (seedPools != null) console.log(` target pools=${seedPools}`)
|
|
if (targetQuotePerPool != null) console.log(` target USDC per pool=${fmt(targetQuotePerPool)}`)
|
|
console.log(` loop count=${loopCount}`)
|
|
console.log('')
|
|
|
|
const direct = baseInRequiredForQuoteOut(B, Q, totalQuoteTarget, lpFeeBps)
|
|
if (!direct.feasible) {
|
|
console.log(' Target quote exceeds available source quote reserve; cannot fully harvest that much in this PMM.')
|
|
return
|
|
}
|
|
|
|
console.log(' Single-shot requirement from current reserves:')
|
|
console.log(` gross cW base sacrifice=${fmt(direct.grossBaseIn)}`)
|
|
console.log(` net cW base into pool=${fmt(direct.deltaBaseNet)}`)
|
|
console.log(` LP fee paid in base=${fmt(direct.feeBase)}`)
|
|
console.log(` realized USDC per gross cW base=${fmt(direct.vwapQuotePerBase)}`)
|
|
printInventoryCoverage(inventoryBase, direct.grossBaseIn)
|
|
console.log('')
|
|
|
|
const splitTargets =
|
|
targetQuotePerPool != null && seedPools != null && loopCount === seedPools
|
|
? Array.from({ length: loopCount }, () => targetQuotePerPool)
|
|
: Array.from({ length: loopCount }, () => totalQuoteTarget / loopCount)
|
|
|
|
let runningB = B
|
|
let runningQ = Q
|
|
let cumulativeQuote = 0
|
|
let cumulativeGrossBase = 0
|
|
let cumulativeNetBase = 0
|
|
let cumulativeFeeBase = 0
|
|
|
|
console.log(' Liquidity loop table:')
|
|
console.log(
|
|
'loop\tquote_target\tgross_base_in\tnet_base_in\tfee_base\tUSDC/gross_base\tend_B\tend_Q\tcum_quote\tcum_gross_base',
|
|
)
|
|
for (let i = 0; i < splitTargets.length; i += 1) {
|
|
const quoteTarget = splitTargets[i]
|
|
const leg = baseInRequiredForQuoteOut(runningB, runningQ, quoteTarget, lpFeeBps)
|
|
if (!leg.feasible) {
|
|
console.log(`${i + 1}\t${fmt(quoteTarget)}\tINFEASIBLE\tINFEASIBLE\tINFEASIBLE\tINFEASIBLE\t${fmt(runningB)}\t${fmt(runningQ)}\t${fmt(cumulativeQuote)}\t${fmt(cumulativeGrossBase)}`)
|
|
break
|
|
}
|
|
runningB += leg.deltaBaseNet
|
|
runningQ -= quoteTarget
|
|
cumulativeQuote += quoteTarget
|
|
cumulativeGrossBase += leg.grossBaseIn
|
|
cumulativeNetBase += leg.deltaBaseNet
|
|
cumulativeFeeBase += leg.feeBase
|
|
console.log(
|
|
[
|
|
i + 1,
|
|
fmt(quoteTarget),
|
|
fmt(leg.grossBaseIn),
|
|
fmt(leg.deltaBaseNet),
|
|
fmt(leg.feeBase),
|
|
fmt(leg.vwapQuotePerBase),
|
|
fmt(runningB),
|
|
fmt(runningQ),
|
|
fmt(cumulativeQuote),
|
|
fmt(cumulativeGrossBase),
|
|
].join('\t'),
|
|
)
|
|
}
|
|
console.log('')
|
|
console.log(' End state after loops:')
|
|
console.log(` total quote raised=${fmt(cumulativeQuote)}`)
|
|
console.log(` cumulative gross cW base sacrifice=${fmt(cumulativeGrossBase)}`)
|
|
console.log(` cumulative net cW base into source pool=${fmt(cumulativeNetBase)}`)
|
|
console.log(` cumulative LP fee paid in base=${fmt(cumulativeFeeBase)}`)
|
|
console.log(` final source reserves: B=${fmt(runningB)} Q=${fmt(runningQ)}`)
|
|
printInventoryCoverage(inventoryBase, cumulativeGrossBase, 'Looped cW base inventory required')
|
|
console.log('')
|
|
}
|
|
|
|
function printSeedingPlanExternal(
|
|
totalQuoteTarget,
|
|
externalPrice,
|
|
exitFeeBps,
|
|
inventoryBase,
|
|
loopCount,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
) {
|
|
const pEff = effectiveExternalPrice(externalPrice, exitFeeBps)
|
|
console.log('Quote seeding plan — external monetization rate\n')
|
|
console.log(` gross external price=${fmt(externalPrice)} USDC per cW base`)
|
|
console.log(` net effective price=${fmt(pEff)} USDC per cW base`)
|
|
console.log(` target USDC to raise=${fmt(totalQuoteTarget)}`)
|
|
if (seedPools != null) console.log(` target pools=${seedPools}`)
|
|
if (targetQuotePerPool != null) console.log(` target USDC per pool=${fmt(targetQuotePerPool)}`)
|
|
console.log(` loop count=${loopCount}`)
|
|
console.log('')
|
|
|
|
if (!(pEff > 0)) {
|
|
console.log(' Effective monetization price is zero or negative; cannot size required cW inventory.')
|
|
return
|
|
}
|
|
|
|
const grossBaseRequired = totalQuoteTarget / pEff
|
|
console.log(` gross cW base sacrifice required=${fmt(grossBaseRequired)}`)
|
|
console.log(` realized USDC per gross cW base=${fmt(pEff)}`)
|
|
printInventoryCoverage(inventoryBase, grossBaseRequired)
|
|
console.log('')
|
|
|
|
if (loopCount > 1) {
|
|
const perLoopQuote = totalQuoteTarget / loopCount
|
|
const perLoopBase = perLoopQuote / pEff
|
|
let cumulativeQuote = 0
|
|
let cumulativeBase = 0
|
|
console.log(' Liquidity loop table:')
|
|
console.log('loop\tquote_target\tgross_base_in\tUSDC/gross_base\tcum_quote\tcum_gross_base')
|
|
for (let i = 0; i < loopCount; i += 1) {
|
|
cumulativeQuote += perLoopQuote
|
|
cumulativeBase += perLoopBase
|
|
console.log(
|
|
[i + 1, fmt(perLoopQuote), fmt(perLoopBase), fmt(pEff), fmt(cumulativeQuote), fmt(cumulativeBase)].join(
|
|
'\t',
|
|
),
|
|
)
|
|
}
|
|
console.log('')
|
|
}
|
|
}
|
|
|
|
function printFundingReadiness({
|
|
inventoryAsset,
|
|
sourceAsset,
|
|
quoteAsset,
|
|
totalQuoteTarget,
|
|
lpFeeBps,
|
|
baseReserve,
|
|
quoteReserve,
|
|
reserveSource,
|
|
poolAddress,
|
|
report,
|
|
reportPath,
|
|
}) {
|
|
const mainnetPool = loadMainnetPool(inventoryAsset, quoteAsset)
|
|
const direct = baseInRequiredForQuoteOut(baseReserve, quoteReserve, totalQuoteTarget, lpFeeBps)
|
|
const deployerAddress = resolveDeployerAddressViaCast()
|
|
const mainnetTokenAddress = mainnetPool?.baseAddress ?? null
|
|
const mainnetQuoteAddress = mainnetPool?.quoteAddress ?? null
|
|
const chain138TokenAddress = loadChain138TokenAddress(sourceAsset)
|
|
const mainnetBalance = resolveTokenBalance({
|
|
chainId: 1,
|
|
symbol: inventoryAsset,
|
|
tokenAddress: mainnetTokenAddress,
|
|
report,
|
|
reportPath,
|
|
deployerAddress,
|
|
})
|
|
const chain138Balance = resolveTokenBalance({
|
|
chainId: 138,
|
|
symbol: sourceAsset,
|
|
tokenAddress: chain138TokenAddress,
|
|
report,
|
|
reportPath,
|
|
deployerAddress,
|
|
})
|
|
|
|
const inventoryDecimals =
|
|
mainnetBalance?.decimals ?? tryReadTokenDecimalsViaCast(1, mainnetTokenAddress) ?? 6
|
|
const sourceDecimals =
|
|
chain138Balance?.decimals ?? tryReadTokenDecimalsViaCast(138, chain138TokenAddress) ?? inventoryDecimals
|
|
const quoteDecimals = tryReadTokenDecimalsViaCast(1, mainnetQuoteAddress) ?? inventoryDecimals
|
|
|
|
const mainnetAvailableHuman = mainnetBalance?.balance ?? 0
|
|
const chain138AvailableHuman = chain138Balance?.balance ?? 0
|
|
const mainnetAvailableRawOnPoolScale = humanToRaw(mainnetAvailableHuman, inventoryDecimals)
|
|
const chain138AvailableRawOnPoolScale = humanToRaw(chain138AvailableHuman, inventoryDecimals)
|
|
const mainnetImmediateQuoteRaw =
|
|
mainnetAvailableRawOnPoolScale > 0
|
|
? baseInToQuoteOut(baseReserve, quoteReserve, mainnetAvailableRawOnPoolScale, lpFeeBps).quoteOut
|
|
: 0
|
|
const bridgeReachableQuoteRaw =
|
|
chain138AvailableRawOnPoolScale > 0
|
|
? baseInToQuoteOut(baseReserve, quoteReserve, chain138AvailableRawOnPoolScale, lpFeeBps).quoteOut
|
|
: 0
|
|
const requiredGrossHuman = rawToHuman(direct.grossBaseIn, inventoryDecimals)
|
|
const requiredNetHuman = rawToHuman(direct.deltaBaseNet, inventoryDecimals)
|
|
const feeBaseHuman = rawToHuman(direct.feeBase, inventoryDecimals)
|
|
const mainnetEnough = direct.feasible && mainnetAvailableHuman >= requiredGrossHuman
|
|
const bridgeEnough = direct.feasible && chain138AvailableHuman >= requiredGrossHuman
|
|
|
|
console.log('Funding readiness — live mainnet vs bridge-reachable inventory\n')
|
|
console.log(` inventory asset=${inventoryAsset} source asset=${sourceAsset} quote asset=${quoteAsset}`)
|
|
console.log(` target ${quoteAsset} to raise=${fmtAmount(totalQuoteTarget, quoteDecimals, quoteAsset)}`)
|
|
console.log(` pool fee=${lpFeeBps} bps`)
|
|
console.log(
|
|
` mainnet pool reserves: B=${fmtAmount(baseReserve, inventoryDecimals, inventoryAsset)}, Q=${fmtAmount(quoteReserve, quoteDecimals, quoteAsset)}`,
|
|
)
|
|
console.log(` reserve source=${reserveSource}`)
|
|
if (poolAddress) console.log(` mainnet pool=${poolAddress}`)
|
|
if (deployerAddress) console.log(` deployer=${deployerAddress}`)
|
|
if (reportPath) console.log(` balance report fallback=${reportPath}`)
|
|
console.log('')
|
|
|
|
if (!direct.feasible) {
|
|
console.log(' Target quote exceeds available quote reserve in the current PMM; no readiness conclusion can be made.')
|
|
console.log('')
|
|
return
|
|
}
|
|
|
|
console.log(' Requirement at current live pool state:')
|
|
console.log(` required gross ${inventoryAsset}=${fmt(requiredGrossHuman)} ${inventoryAsset} (raw ${fmt(direct.grossBaseIn)})`)
|
|
console.log(` required net ${inventoryAsset} into pool=${fmt(requiredNetHuman)} ${inventoryAsset} (raw ${fmt(direct.deltaBaseNet)})`)
|
|
console.log(` implied LP fee in base=${fmt(feeBaseHuman)} ${inventoryAsset} (raw ${fmt(direct.feeBase)})`)
|
|
console.log(` realized ${quoteAsset} per gross ${inventoryAsset}=${fmt(direct.vwapQuotePerBase)}`)
|
|
console.log('')
|
|
|
|
console.log(' View 1 — already funded on mainnet today:')
|
|
console.log(` available ${inventoryAsset} on mainnet=${fmt(mainnetAvailableHuman)} ${inventoryAsset}${mainnetBalance?.raw != null ? ` (raw ${fmt(mainnetBalance.raw)})` : ''}`)
|
|
console.log(` balance source=${mainnetBalance?.source ?? 'unavailable'}`)
|
|
console.log(` immediate quote capacity at current pool=${fmt(rawToHuman(mainnetImmediateQuoteRaw, quoteDecimals))} ${quoteAsset} (raw ${fmt(mainnetImmediateQuoteRaw)})`)
|
|
console.log(
|
|
` status=${mainnetEnough ? 'enough now' : 'short'} (${mainnetEnough ? 'remaining' : 'shortfall'} ${fmt(Math.abs(mainnetAvailableHuman - requiredGrossHuman))} ${inventoryAsset})`,
|
|
)
|
|
console.log('')
|
|
|
|
console.log(' View 2 — reachable after bridging from current Chain 138 inventory:')
|
|
console.log(` available ${sourceAsset} on Chain 138=${fmt(chain138AvailableHuman)} ${sourceAsset}${chain138Balance?.raw != null ? ` (raw ${fmt(chain138Balance.raw)})` : ''}`)
|
|
console.log(` balance source=${chain138Balance?.source ?? 'unavailable'}`)
|
|
console.log(` bridge-reachable quote capacity at current pool=${fmt(rawToHuman(bridgeReachableQuoteRaw, quoteDecimals))} ${quoteAsset} (raw ${fmt(bridgeReachableQuoteRaw)})`)
|
|
console.log(
|
|
` status=${bridgeEnough ? 'enough after bridge' : 'short'} (${bridgeEnough ? 'remaining' : 'shortfall'} ${fmt(Math.abs(chain138AvailableHuman - requiredGrossHuman))} ${sourceAsset})`,
|
|
)
|
|
console.log('')
|
|
console.log(' Assumption for View 2: 1:1 nominal c* -> cW* corridor, ignoring bridge fees, caps, and transfer latency.')
|
|
console.log('')
|
|
}
|
|
|
|
function buildQuoteDeploymentSteps({
|
|
retainedQuote,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
}) {
|
|
if (targetQuotePerPool == null && targetTotalQuote == null) return null
|
|
|
|
const totalTarget =
|
|
targetTotalQuote != null
|
|
? targetTotalQuote
|
|
: (seedPools ?? loopCount ?? 1) * targetQuotePerPool
|
|
const steps =
|
|
targetQuotePerPool != null && seedPools != null
|
|
? Array.from({ length: seedPools }, (_, index) => ({
|
|
label: `pool_${index + 1}`,
|
|
targetQuote: targetQuotePerPool,
|
|
}))
|
|
: Array.from({ length: loopCount ?? 1 }, (_, index) => ({
|
|
label: `loop_${index + 1}`,
|
|
targetQuote: totalTarget / (loopCount ?? 1),
|
|
}))
|
|
|
|
let remainingQuote = Math.max(0, retainedQuote)
|
|
const rows = steps.map((step) => {
|
|
const deployedQuote = Math.min(step.targetQuote, remainingQuote)
|
|
remainingQuote -= deployedQuote
|
|
return {
|
|
...step,
|
|
deployedQuote,
|
|
shortfallQuote: step.targetQuote - deployedQuote,
|
|
fullyFunded: deployedQuote >= step.targetQuote,
|
|
remainingQuote,
|
|
}
|
|
})
|
|
|
|
const fullyFundedCount = rows.filter((row) => row.fullyFunded).length
|
|
const deployedTotal = rows.reduce((sum, row) => sum + row.deployedQuote, 0)
|
|
return {
|
|
totalTarget,
|
|
rows,
|
|
fullyFundedCount,
|
|
deployedTotal,
|
|
remainingQuote,
|
|
shortfallQuote: Math.max(0, totalTarget - deployedTotal),
|
|
}
|
|
}
|
|
|
|
function printQuoteDeploymentPlan({
|
|
retainedQuote,
|
|
quoteAsset,
|
|
quoteDecimals,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
}) {
|
|
const plan = buildQuoteDeploymentSteps({
|
|
retainedQuote,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
})
|
|
if (!plan) {
|
|
console.log(` no destination pool target provided; retained ${quoteAsset} remains unallocated`)
|
|
return
|
|
}
|
|
|
|
console.log('Destination pool deployment')
|
|
console.log(` retained ${quoteAsset} available=${fmtAmount(retainedQuote, quoteDecimals, quoteAsset)}`)
|
|
if (seedPools != null) console.log(` target pools=${seedPools}`)
|
|
if (targetQuotePerPool != null) console.log(` target ${quoteAsset} per pool=${fmtAmount(targetQuotePerPool, quoteDecimals, quoteAsset)}`)
|
|
if (targetTotalQuote != null) console.log(` target total ${quoteAsset}=${fmtAmount(targetTotalQuote, quoteDecimals, quoteAsset)}`)
|
|
if (loopCount != null) console.log(` deployment loops=${loopCount}`)
|
|
console.log(
|
|
` status=${plan.shortfallQuote > 0 ? 'short' : 'enough'} (${plan.shortfallQuote > 0 ? 'shortfall' : 'leftover'} ${fmtAmount(
|
|
plan.shortfallQuote > 0 ? plan.shortfallQuote : plan.remainingQuote,
|
|
quoteDecimals,
|
|
quoteAsset,
|
|
)})`,
|
|
)
|
|
if (targetQuotePerPool != null) {
|
|
console.log(` fully funded pools=${plan.fullyFundedCount}`)
|
|
}
|
|
console.log('')
|
|
console.log('step\ttarget_quote\tdeployed_quote\tshortfall_quote\tremaining_quote\tstatus')
|
|
for (const row of plan.rows) {
|
|
console.log(
|
|
[
|
|
row.label,
|
|
fmtAmount(row.targetQuote, quoteDecimals, quoteAsset),
|
|
fmtAmount(row.deployedQuote, quoteDecimals, quoteAsset),
|
|
fmtAmount(row.shortfallQuote, quoteDecimals, quoteAsset),
|
|
fmtAmount(row.remainingQuote, quoteDecimals, quoteAsset),
|
|
row.fullyFunded ? 'funded' : 'partial',
|
|
].join('\t'),
|
|
)
|
|
}
|
|
console.log('')
|
|
}
|
|
|
|
function simulateFullLoopDryRun({
|
|
B,
|
|
Q,
|
|
x,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
loopStrategy,
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
externalEntryPrice,
|
|
externalExitPrice,
|
|
externalEntryFeeBps,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
quoteAsset,
|
|
baseAsset,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
guardConfig,
|
|
flashProviderCap,
|
|
flashProviderName,
|
|
flashFeeAmount,
|
|
flashFeeSource,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset,
|
|
atomicDestinationDecimals,
|
|
atomicCorridorLabel,
|
|
}) {
|
|
const repay = repayUsdcWithExactFee(x, flashFeeAmount, flashFeeBps)
|
|
const effectiveBaseReserve = B + ownedBaseAdd
|
|
const effectiveQuoteReserve = Q + flashTopupQuote
|
|
const strategyQuote = Math.max(0, x - flashTopupQuote)
|
|
const strategyLabel = loopStrategy === 'base-unwind' ? 'base-unwind' : 'quote-push'
|
|
|
|
let leg
|
|
let externalLeg
|
|
let availableForRepay
|
|
let endBaseReserve
|
|
let endQuoteReserve
|
|
let step2MetricA
|
|
let step2MetricB
|
|
let step2MetricC
|
|
let step3MetricA
|
|
let step3MetricB
|
|
let strategyNote
|
|
let atomicBaseReserved = 0
|
|
let atomicBaseRequested = atomicBridgeBaseAmount ?? 0
|
|
let atomicDestinationFulfilled = 0
|
|
let unwindBaseAmount = 0
|
|
let atomicBridgeShortfall = 0
|
|
|
|
if (strategyLabel === 'base-unwind') {
|
|
externalLeg = externalQuoteToBaseOut(
|
|
strategyQuote,
|
|
externalEntryPrice,
|
|
externalEntryFeeBps,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
)
|
|
leg = baseInToQuoteOut(effectiveBaseReserve, effectiveQuoteReserve, externalLeg.baseOut, lpFeeBps)
|
|
availableForRepay = leg.quoteOut
|
|
endBaseReserve = effectiveBaseReserve + leg.deltaBaseNet
|
|
endQuoteReserve = Math.max(0, effectiveQuoteReserve - leg.quoteOut)
|
|
step2MetricA = ['2', `external_route_${formatExternalRoute(quoteAsset, baseAsset, externalViaAsset).replaceAll(' ', '_')}`, fmtAmount(strategyQuote, quoteDecimals, quoteAsset)]
|
|
step2MetricB = ['2', 'external_entry_price_effective', fmt(externalLeg.effectivePrice)]
|
|
step2MetricC = ['2', 'external_base_acquired', fmtAmount(externalLeg.baseOut, baseDecimals, baseAsset)]
|
|
step3MetricA = ['3', 'pmm_base_in_net', fmtAmount(leg.deltaBaseNet, baseDecimals, baseAsset)]
|
|
step3MetricB = ['3', 'pmm_quote_out', fmtAmount(leg.quoteOut, quoteDecimals, quoteAsset)]
|
|
strategyNote =
|
|
ownedBaseAdd > 0
|
|
? ' note: this is the owned-base-add + external-acquire -> PMM sell-base path, so the source pool starts deeper on base, then gains more base reserve and loses quote reserve. Any temporary flash quote top-up left in the source pool is bounded by the PMM quote out used for repayment.'
|
|
: ' note: this is the external-acquire -> PMM sell-base path, so the source pool gains base reserve and loses quote reserve. Any temporary flash quote top-up left in the source pool is bounded by the PMM quote out used for repayment.'
|
|
} else {
|
|
leg = quoteInToBaseOut(effectiveBaseReserve, effectiveQuoteReserve, strategyQuote, lpFeeBps)
|
|
atomicBaseReserved = Math.min(Math.max(0, atomicBaseRequested), leg.baseOut)
|
|
atomicBridgeShortfall = Math.max(0, atomicBaseRequested - leg.baseOut)
|
|
unwindBaseAmount = Math.max(0, leg.baseOut - atomicBaseReserved)
|
|
atomicDestinationFulfilled =
|
|
atomicBaseReserved > 0
|
|
? (atomicDestinationAmount ??
|
|
convertRawAmount(
|
|
atomicBaseReserved,
|
|
baseDecimals,
|
|
atomicDestinationDecimals ?? baseDecimals,
|
|
))
|
|
: 0
|
|
externalLeg = externalBaseToQuoteOut(
|
|
unwindBaseAmount,
|
|
externalExitPrice,
|
|
externalExitFeeBps,
|
|
baseDecimals,
|
|
quoteDecimals,
|
|
)
|
|
availableForRepay = externalLeg.quoteOut
|
|
endBaseReserve = effectiveBaseReserve - leg.baseOut
|
|
endQuoteReserve = effectiveQuoteReserve + leg.deltaXNet
|
|
step2MetricA = ['2', 'pmm_quote_in_net', fmtAmount(leg.deltaXNet, quoteDecimals, quoteAsset)]
|
|
step2MetricB = ['2', 'pmm_base_out', fmtAmount(leg.baseOut, baseDecimals, baseAsset)]
|
|
step2MetricC = ['2', 'pmm_vwap_quote_per_base', fmt(rawToHuman(strategyQuote, quoteDecimals) / rawToHuman(leg.baseOut, baseDecimals))]
|
|
step3MetricA = ['3', `external_route_${formatExternalRoute(baseAsset, quoteAsset, externalViaAsset).replaceAll(' ', '_')}`, fmtAmount(unwindBaseAmount, baseDecimals, baseAsset)]
|
|
step3MetricB = ['3', 'external_exit_price_effective', fmt(externalLeg.effectivePrice)]
|
|
strategyNote =
|
|
ownedBaseAdd > 0
|
|
? ' note: this is the owned-base-add + flash-backed quote-in path, so the source pool starts deeper on base, then permanently gains quote reserve and loses some base reserve if the external unwind covers repayment.'
|
|
: ' note: this is the flash-backed quote-in path, so the source pool permanently gains quote reserve and loses base reserve if the external unwind covers repayment.'
|
|
if (atomicBaseReserved > 0) {
|
|
strategyNote += ` ${fmtAmount(atomicBaseReserved, baseDecimals, baseAsset)} is reserved into the atomic corridor${atomicCorridorLabel ? ` (${atomicCorridorLabel})` : ''}, and only the remaining ${fmtAmount(unwindBaseAmount, baseDecimals, baseAsset)} is unwound back into ${quoteAsset} for flash repayment.`
|
|
}
|
|
}
|
|
|
|
const retainedBeforeGas = availableForRepay - repay
|
|
const retainedAfterGas = retainedBeforeGas - guardConfig.gasReserveQuote
|
|
const postTradeMarginalPrice =
|
|
rawToHuman(endBaseReserve, baseDecimals) > 0
|
|
? rawToHuman(endQuoteReserve, quoteDecimals) / rawToHuman(endBaseReserve, baseDecimals)
|
|
: NaN
|
|
const postTradeDeviationBps = deviationBps(postTradeMarginalPrice, guardConfig.oraclePrice)
|
|
const postTradeDeviationAbsBps = Math.abs(postTradeDeviationBps)
|
|
const gasReserveSummary = summarizeGasReserve(guardConfig)
|
|
const providerCapOk = flashProviderCap == null ? null : x <= flashProviderCap
|
|
const effectiveFlashFeeBps = flashFeeAmount != null ? approxBpsFromAmount(x, flashFeeAmount) : flashFeeBps
|
|
const deviationOk =
|
|
guardConfig.maxPostTradeDeviationBps == null
|
|
? null
|
|
: postTradeDeviationAbsBps <= guardConfig.maxPostTradeDeviationBps
|
|
const deploymentPlan = buildQuoteDeploymentSteps({
|
|
retainedQuote: retainedAfterGas,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
})
|
|
const retainedFloorActive = guardConfig.minRetainedUsdc > 0
|
|
const retainedFloorOk = retainedAfterGas >= guardConfig.minRetainedUsdc
|
|
const deploymentOk =
|
|
deploymentPlan == null ? null : deploymentPlan.shortfallQuote <= 0
|
|
|
|
const guardFailures = []
|
|
if (providerCapOk === false) guardFailures.push('flash_cap')
|
|
if (!(availableForRepay >= repay)) guardFailures.push('repay')
|
|
if (!(retainedAfterGas >= 0)) guardFailures.push('gas')
|
|
if (retainedFloorActive && !retainedFloorOk) guardFailures.push('retained_floor')
|
|
if (deviationOk === false) guardFailures.push('post_trade_dev')
|
|
if (deploymentOk === false) guardFailures.push('deploy_target')
|
|
if (strategyLabel !== 'quote-push' && atomicBaseRequested > 0) guardFailures.push('atomic_bridge_strategy')
|
|
if (atomicBridgeShortfall > 0) guardFailures.push('atomic_bridge_base')
|
|
|
|
return {
|
|
strategyLabel,
|
|
effectiveBaseReserve,
|
|
effectiveQuoteReserve,
|
|
strategyQuote,
|
|
repay,
|
|
retainedBeforeGas,
|
|
retainedAfterGas,
|
|
endBaseReserve,
|
|
endQuoteReserve,
|
|
postTradeMarginalPrice,
|
|
postTradeDeviationBps,
|
|
postTradeDeviationAbsBps,
|
|
gasReserveSummary,
|
|
providerCapOk,
|
|
effectiveFlashFeeBps,
|
|
deviationOk,
|
|
deploymentPlan,
|
|
retainedFloorActive,
|
|
retainedFloorOk,
|
|
deploymentOk,
|
|
guardFailures,
|
|
leg,
|
|
externalLeg,
|
|
availableForRepay,
|
|
step2MetricA,
|
|
step2MetricB,
|
|
step2MetricC,
|
|
step3MetricA,
|
|
step3MetricB,
|
|
strategyNote,
|
|
atomicBaseRequested,
|
|
atomicBaseReserved,
|
|
atomicBridgeShortfall,
|
|
atomicDestinationFulfilled,
|
|
atomicDestinationAsset: atomicDestinationAsset ?? baseAsset,
|
|
atomicDestinationDecimals: atomicDestinationDecimals ?? baseDecimals,
|
|
atomicCorridorLabel,
|
|
unwindBaseAmount,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whitepaper-style ladder: repeated quote-push full-loop dry-runs; after each iteration,
|
|
* rebalance reserves to matched at the post-trade quote depth (B := Q := endQuoteReserve).
|
|
* Flash size per step: round(matched * flashNumerator / flashDenominator); defaults match
|
|
* MAINNET_CWUSD_HYBRID_FLASH_LOOP_CALCULATION_WHITEPAPER.md row 0→1 (6342691 / 256763253).
|
|
*/
|
|
function printSequentialMatchedLadder({
|
|
iterations,
|
|
startB,
|
|
startQ,
|
|
flashNumerator,
|
|
flashDenominator,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
externalExitPrice,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
quoteAsset,
|
|
baseAsset,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
guardConfig,
|
|
flashProviderCap,
|
|
flashProviderName,
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset,
|
|
atomicDestinationDecimals,
|
|
atomicCorridorLabel,
|
|
}) {
|
|
if (startB !== startQ) {
|
|
console.error('Error: --sequential-matched-loops requires matched -B and -Q (equal raw reserves)')
|
|
process.exit(1)
|
|
}
|
|
if (!(flashNumerator > 0) || !(flashDenominator > 0)) {
|
|
console.error('Error: matched flash fraction numerator/denominator must be positive')
|
|
process.exit(1)
|
|
}
|
|
|
|
let B = startB
|
|
let Q = startQ
|
|
let cumRetained = 0
|
|
let cumFlash = 0
|
|
let cumBaseRebalance = 0
|
|
let completed = 0
|
|
let firstFailure = null
|
|
|
|
console.log('Sequential matched-ladder simulation (modeled quote-push + rebalance B=Q=endQuote)\n')
|
|
console.log(` iterations=${iterations}`)
|
|
console.log(
|
|
` flash sizing: x=round(matched*${flashNumerator}/${flashDenominator}) raw ${quoteAsset} per loop`,
|
|
)
|
|
console.log(` start matched=${rawToHuman(B, quoteDecimals)} ${baseAsset} / ${rawToHuman(Q, quoteDecimals)} ${quoteAsset} (raw ${B})\n`)
|
|
|
|
for (let i = 1; i <= iterations; i++) {
|
|
const matched = Math.min(B, Q)
|
|
const x = Math.max(1, Math.round((matched * flashNumerator) / flashDenominator))
|
|
const sim = simulateFullLoopDryRun({
|
|
B,
|
|
Q,
|
|
x,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
loopStrategy: 'quote-push',
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
externalEntryPrice: null,
|
|
externalExitPrice,
|
|
externalEntryFeeBps: 0,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
quoteAsset,
|
|
baseAsset,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
guardConfig,
|
|
flashProviderCap,
|
|
flashProviderName,
|
|
flashFeeAmount: null,
|
|
flashFeeSource: null,
|
|
seedPools: null,
|
|
targetQuotePerPool: null,
|
|
targetTotalQuote: null,
|
|
loopCount: null,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset,
|
|
atomicDestinationDecimals,
|
|
atomicCorridorLabel,
|
|
})
|
|
|
|
cumRetained += sim.retainedAfterGas
|
|
cumFlash += x
|
|
const baseAddRaw = Math.max(0, Math.round(sim.endQuoteReserve - sim.endBaseReserve))
|
|
cumBaseRebalance += baseAddRaw
|
|
completed = i
|
|
|
|
if (sim.guardFailures.length > 0) {
|
|
firstFailure = { i, x, sim }
|
|
break
|
|
}
|
|
|
|
const nextMatched = Math.round(sim.endQuoteReserve)
|
|
B = nextMatched
|
|
Q = nextMatched
|
|
}
|
|
|
|
const endMatchedHuman = rawToHuman(B, quoteDecimals)
|
|
console.log('Summary')
|
|
console.log(` completed_loops=${completed}${firstFailure ? ` (stopped at failure on loop ${firstFailure.i})` : ''}`)
|
|
console.log(
|
|
` end_matched_pool=${fmtAmount(B, quoteDecimals, baseAsset)} / ${fmtAmount(Q, quoteDecimals, quoteAsset)} (human ${endMatchedHuman} each side)`,
|
|
)
|
|
console.log(` cumulative_retained_after_gas=${fmtAmount(cumRetained, quoteDecimals, quoteAsset)}`)
|
|
console.log(` cumulative_flash_borrowed=${fmtAmount(cumFlash, quoteDecimals, quoteAsset)}`)
|
|
console.log(
|
|
` cumulative_modeled_base_rebalance_raw=${Math.round(cumBaseRebalance)} (${fmtAmount(Math.round(cumBaseRebalance), baseDecimals, baseAsset)} ${baseAsset} added to match quote depth)`,
|
|
)
|
|
console.log('')
|
|
|
|
if (firstFailure) {
|
|
const { i, x, sim } = firstFailure
|
|
console.log(`Loop ${i} failed guards: ${sim.guardFailures.join(', ')} (x=${x} raw)`)
|
|
console.log(
|
|
` post_trade_marginal_price=${fmt(sim.postTradeMarginalPrice)} quote/base, deviation_bps=${fmt(sim.postTradeDeviationBps)}`,
|
|
)
|
|
process.exit(2)
|
|
}
|
|
}
|
|
|
|
function deriveEstimatedGasQuoteForMainnetStrategy({
|
|
strategy,
|
|
poolAddress,
|
|
baseAddress,
|
|
quoteAddress,
|
|
amountInQuoteRaw,
|
|
externalEntryPrice,
|
|
externalEntryFeeBps,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
nativeTokenPrice,
|
|
fromAddress,
|
|
}) {
|
|
if (!(nativeTokenPrice > 0)) return null
|
|
if (strategy === 'quote-push') {
|
|
return tryEstimateMainnetIntegrationGasQuote({
|
|
poolAddress,
|
|
tokenIn: quoteAddress,
|
|
amountIn: amountInQuoteRaw,
|
|
quoteDecimals,
|
|
nativeTokenPrice,
|
|
fromAddress,
|
|
})
|
|
}
|
|
if (strategy === 'base-unwind' && externalEntryPrice != null) {
|
|
const externalLeg = externalQuoteToBaseOut(
|
|
amountInQuoteRaw,
|
|
externalEntryPrice,
|
|
externalEntryFeeBps ?? 0,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
)
|
|
return tryEstimateMainnetIntegrationGasQuote({
|
|
poolAddress,
|
|
tokenIn: baseAddress,
|
|
amountIn: externalLeg.baseOut,
|
|
quoteDecimals,
|
|
nativeTokenPrice,
|
|
fromAddress,
|
|
})
|
|
}
|
|
return null
|
|
}
|
|
|
|
function scaleFixableGuardFailures(guardFailures) {
|
|
return guardFailures.filter((failure) => failure !== 'flash_cap')
|
|
}
|
|
|
|
function findMinimumUniformDepthMultiplierForPass(params, maxFactor = 1e6, iterations = 40) {
|
|
const baseline = simulateFullLoopDryRun(params)
|
|
if (scaleFixableGuardFailures(baseline.guardFailures).length === 0) {
|
|
return {
|
|
factor: 1,
|
|
addedBase: 0,
|
|
addedQuote: 0,
|
|
simulation: baseline,
|
|
}
|
|
}
|
|
|
|
let lo = 1
|
|
let hi = 1
|
|
let hiSimulation = baseline
|
|
|
|
while (hi < maxFactor) {
|
|
hi *= 2
|
|
hiSimulation = simulateFullLoopDryRun({
|
|
...params,
|
|
B: params.B * hi,
|
|
Q: params.Q * hi,
|
|
})
|
|
if (scaleFixableGuardFailures(hiSimulation.guardFailures).length === 0) break
|
|
}
|
|
|
|
if (scaleFixableGuardFailures(hiSimulation.guardFailures).length !== 0) {
|
|
return null
|
|
}
|
|
|
|
for (let i = 0; i < iterations; i += 1) {
|
|
const mid = (lo + hi) / 2
|
|
const midSimulation = simulateFullLoopDryRun({
|
|
...params,
|
|
B: params.B * mid,
|
|
Q: params.Q * mid,
|
|
})
|
|
if (scaleFixableGuardFailures(midSimulation.guardFailures).length === 0) {
|
|
hi = mid
|
|
hiSimulation = midSimulation
|
|
} else {
|
|
lo = mid
|
|
}
|
|
}
|
|
|
|
return {
|
|
factor: hi,
|
|
addedBase: params.B * (hi - 1),
|
|
addedQuote: params.Q * (hi - 1),
|
|
simulation: hiSimulation,
|
|
}
|
|
}
|
|
|
|
function findMaxAtomicBridgeReservation(params, maxBaseReserve = null, iterations = 40) {
|
|
if (params.loopStrategy !== 'quote-push') return null
|
|
|
|
const baseline = simulateFullLoopDryRun({
|
|
...params,
|
|
atomicBridgeBaseAmount: 0,
|
|
atomicDestinationAmount: 0,
|
|
})
|
|
const availableBase = Math.max(0, maxBaseReserve ?? baseline.leg?.baseOut ?? 0)
|
|
if (!(availableBase > 0)) {
|
|
return {
|
|
maxBaseAvailable: 0,
|
|
positiveAfterGas: { amount: 0, simulation: baseline },
|
|
repayOnly: { amount: 0, simulation: baseline },
|
|
}
|
|
}
|
|
|
|
const positivePredicate = (simulation) =>
|
|
simulation.guardFailures.length === 0
|
|
|
|
const repayOnlyPredicate = (simulation) => {
|
|
const failures = simulation.guardFailures.filter((failure) => !['gas', 'retained_floor', 'deploy_target'].includes(failure))
|
|
return failures.length === 0 && simulation.availableForRepay >= simulation.repay
|
|
}
|
|
|
|
function solve(predicate) {
|
|
let lo = 0
|
|
let hi = availableBase
|
|
let bestSimulation = baseline
|
|
for (let i = 0; i < iterations; i += 1) {
|
|
const mid = Math.floor((lo + hi + 1) / 2)
|
|
const simulation = simulateFullLoopDryRun({
|
|
...params,
|
|
atomicBridgeBaseAmount: mid,
|
|
atomicDestinationAmount: params.atomicDestinationAmount ?? mid,
|
|
})
|
|
if (predicate(simulation)) {
|
|
lo = mid
|
|
bestSimulation = simulation
|
|
} else {
|
|
hi = mid - 1
|
|
}
|
|
}
|
|
const finalSimulation =
|
|
lo === 0
|
|
? simulateFullLoopDryRun({
|
|
...params,
|
|
atomicBridgeBaseAmount: 0,
|
|
atomicDestinationAmount: params.atomicDestinationAmount ?? 0,
|
|
})
|
|
: bestSimulation
|
|
return { amount: lo, simulation: finalSimulation }
|
|
}
|
|
|
|
return {
|
|
maxBaseAvailable: availableBase,
|
|
positiveAfterGas: solve(positivePredicate),
|
|
repayOnly: solve(repayOnlyPredicate),
|
|
}
|
|
}
|
|
|
|
function fullLoopFailureScore(candidate) {
|
|
const { simulation, x } = candidate
|
|
let score = simulation.guardFailures.length * 1e12
|
|
if (simulation.guardFailures.includes('flash_cap') && candidate.flashProviderCap != null) {
|
|
score += Math.max(0, x - candidate.flashProviderCap) * 1e3
|
|
}
|
|
if (simulation.guardFailures.includes('repay')) {
|
|
score += Math.max(0, simulation.repay - simulation.availableForRepay) * 1e2
|
|
}
|
|
if (simulation.guardFailures.includes('gas')) {
|
|
score += Math.max(0, -simulation.retainedAfterGas) * 10
|
|
}
|
|
if (simulation.guardFailures.includes('retained_floor')) {
|
|
score += Math.max(0, candidate.guardConfig.minRetainedUsdc - simulation.retainedAfterGas) * 10
|
|
}
|
|
if (simulation.guardFailures.includes('deploy_target') && simulation.deploymentPlan?.totalTarget > 0) {
|
|
score +=
|
|
(simulation.deploymentPlan.shortfallQuote / simulation.deploymentPlan.totalTarget) * 1e6
|
|
}
|
|
if (simulation.guardFailures.includes('post_trade_dev')) {
|
|
score += simulation.postTradeDeviationAbsBps
|
|
}
|
|
return score
|
|
}
|
|
|
|
function compareFullLoopCandidates(a, b) {
|
|
const aPass = a.simulation.guardFailures.length === 0
|
|
const bPass = b.simulation.guardFailures.length === 0
|
|
if (aPass !== bPass) return Number(bPass) - Number(aPass)
|
|
|
|
if (aPass && bPass) {
|
|
const aFunded = a.simulation.deploymentPlan?.fullyFundedCount ?? 0
|
|
const bFunded = b.simulation.deploymentPlan?.fullyFundedCount ?? 0
|
|
if (aFunded !== bFunded) return bFunded - aFunded
|
|
|
|
const aDeployed = a.simulation.deploymentPlan?.deployedTotal ?? 0
|
|
const bDeployed = b.simulation.deploymentPlan?.deployedTotal ?? 0
|
|
if (aDeployed !== bDeployed) return bDeployed - aDeployed
|
|
|
|
if (a.simulation.retainedAfterGas !== b.simulation.retainedAfterGas) {
|
|
return b.simulation.retainedAfterGas - a.simulation.retainedAfterGas
|
|
}
|
|
if (a.simulation.postTradeDeviationAbsBps !== b.simulation.postTradeDeviationAbsBps) {
|
|
return a.simulation.postTradeDeviationAbsBps - b.simulation.postTradeDeviationAbsBps
|
|
}
|
|
return b.x - a.x
|
|
}
|
|
|
|
if (a.simulation.guardFailures.length !== b.simulation.guardFailures.length) {
|
|
return a.simulation.guardFailures.length - b.simulation.guardFailures.length
|
|
}
|
|
|
|
const aScore = fullLoopFailureScore(a)
|
|
const bScore = fullLoopFailureScore(b)
|
|
if (aScore !== bScore) return aScore - bScore
|
|
if (a.simulation.postTradeDeviationAbsBps !== b.simulation.postTradeDeviationAbsBps) {
|
|
return a.simulation.postTradeDeviationAbsBps - b.simulation.postTradeDeviationAbsBps
|
|
}
|
|
if (a.simulation.retainedAfterGas !== b.simulation.retainedAfterGas) {
|
|
return b.simulation.retainedAfterGas - a.simulation.retainedAfterGas
|
|
}
|
|
return a.x - b.x
|
|
}
|
|
|
|
function buildFullLoopMethodologies(params, simulation, depthPlan) {
|
|
const methods = []
|
|
const {
|
|
strategyLabel,
|
|
deploymentPlan,
|
|
retainedAfterGas,
|
|
guardFailures,
|
|
availableForRepay,
|
|
repay,
|
|
} = simulation
|
|
const {
|
|
x,
|
|
baseAsset,
|
|
quoteAsset,
|
|
baseDecimals,
|
|
quoteDecimals,
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
flashProviderCap,
|
|
guardConfig,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAsset,
|
|
} = params
|
|
|
|
if (guardFailures.length === 0) {
|
|
methods.push(
|
|
`Repay first, then seed only retained ${quoteAsset}. This run leaves ${fmtAmount(retainedAfterGas, quoteDecimals, quoteAsset)} after gas${deploymentPlan ? ` and can fully fund ${deploymentPlan.fullyFundedCount}/${deploymentPlan.rows.length} planned pool targets` : ''}.`,
|
|
)
|
|
if (ownedBaseAdd > 0) {
|
|
methods.push(
|
|
`The owned ${baseAsset} contribution is durable base-side depth. In this hybrid mode, the flash loan is only temporary ${quoteAsset} working capital while the owned base stays in the pool.`,
|
|
)
|
|
}
|
|
} else {
|
|
if (depthPlan && depthPlan.factor > 1) {
|
|
methods.push(
|
|
`Uniformly deepen the source PMM by about ${fmt(depthPlan.factor)}x before retrying this exact tranche: add roughly ${fmtAmount(depthPlan.addedBase, baseDecimals, baseAsset)} and ${fmtAmount(depthPlan.addedQuote, quoteDecimals, quoteAsset)}.`,
|
|
)
|
|
}
|
|
|
|
if (guardFailures.includes('repay')) {
|
|
methods.push(
|
|
`Improve the external unwind or reduce size before retrying: current proceeds ${fmtAmount(availableForRepay, quoteDecimals, quoteAsset)} do not cover flash repayment ${fmtAmount(repay, quoteDecimals, quoteAsset)}.`,
|
|
)
|
|
}
|
|
if (guardFailures.includes('gas')) {
|
|
methods.push(
|
|
`Reserve or top up native gas before execution. Retained quote after gas is ${fmtAmount(retainedAfterGas, quoteDecimals, quoteAsset)} under the current gas assumptions.`,
|
|
)
|
|
}
|
|
if (guardFailures.includes('retained_floor')) {
|
|
methods.push(
|
|
`Lower the retained-${quoteAsset} floor, improve external execution, or shrink the tranche so the loop leaves at least ${fmtAmount(guardConfig.minRetainedUsdc, quoteDecimals, quoteAsset)} after gas.`,
|
|
)
|
|
}
|
|
if (guardFailures.includes('deploy_target') && deploymentPlan) {
|
|
methods.push(
|
|
`Current retained quote only funds ${deploymentPlan.fullyFundedCount}/${deploymentPlan.rows.length} target pool steps. Either lower the deployment target or accumulate retained ${quoteAsset} off-venue first, then seed the public pools.`,
|
|
)
|
|
}
|
|
if (guardFailures.includes('flash_cap')) {
|
|
methods.push(
|
|
`The requested borrow ${fmtAmount(x, quoteDecimals, quoteAsset)} exceeds the current flash source cap${flashProviderCap != null ? ` ${fmtAmount(flashProviderCap, quoteDecimals, quoteAsset)}` : ''}. Switch flash source or shrink the tranche.`,
|
|
)
|
|
}
|
|
}
|
|
|
|
if (strategyLabel === 'quote-push') {
|
|
methods.push(
|
|
`${strategyLabel} permanently deepens quote reserve Q and drains base reserve B. If your end goal is stronger public quote liquidity, this is the right direction, but seed base-side depth first or use smaller staged tranches so the base side does not become the peg bottleneck.`,
|
|
)
|
|
} else {
|
|
methods.push(
|
|
`${strategyLabel} deepens base reserve B but drains quote reserve Q. Use it when the public pool needs more ${baseAsset}, but pair it with direct ${quoteAsset} seeding or a prior quote-build phase if quote-side liquidity is the end goal.`,
|
|
)
|
|
}
|
|
|
|
if (flashTopupQuote > 0) {
|
|
methods.push(
|
|
`Treat --flash-topup-quote as temporary execution support only. It helps the strategy leg, but it does not create durable pool depth unless retained ${quoteAsset} remains after repayment and is left in the pool on purpose.`,
|
|
)
|
|
}
|
|
|
|
if (ownedBaseAdd > 0) {
|
|
methods.push(
|
|
`Hybrid methodology: add owned ${baseAsset} first, use flash-borrowed ${quoteAsset} only for the execution leg, repay from external ${quoteAsset} proceeds, and leave only retained ${quoteAsset} behind. That is the durable two-sided depth-building path.`,
|
|
)
|
|
}
|
|
|
|
if (atomicBridgeBaseAmount > 0) {
|
|
methods.push(
|
|
`Atomic bridge split methodology: reserve ${fmtAmount(atomicBridgeBaseAmount, baseDecimals, baseAsset)} of acquired ${baseAsset} into the destination corridor${atomicDestinationAsset ? ` for immediate ${atomicDestinationAsset} fulfillment` : ''}, and only unwind the remaining ${baseAsset} back into ${quoteAsset}. This improves cross-chain destination liquidity but reduces same-chain repayment headroom.`,
|
|
)
|
|
}
|
|
|
|
if (guardFailures.includes('post_trade_dev')) {
|
|
methods.push(
|
|
'If the public pool is still canary-sized, run the profit leg on a deeper internal/private venue first and only deploy the retained quote into the public PMM afterward. The flash loop should support deployment, not force the public pool to absorb the whole trade at once.',
|
|
)
|
|
methods.push(
|
|
'Use staged tranches from --scan to find the largest size that stays inside the deviation cap, then only scale size after the prior loop has already deepened the pool.',
|
|
)
|
|
}
|
|
|
|
return [...new Set(methods)]
|
|
}
|
|
|
|
function printFullLoopRecommendation(params, simulation, options = {}) {
|
|
const { executionClass = null, executionGradeRequired = false } = options
|
|
const depthPlan =
|
|
simulation.guardFailures.length === 0 ? null : findMinimumUniformDepthMultiplierForPass(params)
|
|
const methods = buildFullLoopMethodologies(params, simulation, depthPlan)
|
|
const deploymentPlan = simulation.deploymentPlan
|
|
const canaryThreshold = humanToRaw(1, params.quoteDecimals)
|
|
const canarySizedPass =
|
|
simulation.guardFailures.length === 0 &&
|
|
!deploymentPlan &&
|
|
simulation.retainedAfterGas < canaryThreshold
|
|
|
|
console.log('Recommendation')
|
|
if (simulation.guardFailures.length === 0) {
|
|
if (executionGradeRequired && executionClass !== 'execution-ready') {
|
|
console.log(
|
|
` Economically passes as modeled, but do not execute it yet. The tranche at ${fmtAmount(params.x, params.quoteDecimals, params.quoteAsset)} using ${simulation.strategyLabel} is still blocked by the execution-grade gate.`,
|
|
)
|
|
} else if (executionClass != null && executionClass !== 'execution-ready') {
|
|
console.log(
|
|
` Economically passes as modeled at ${fmtAmount(params.x, params.quoteDecimals, params.quoteAsset)} using ${simulation.strategyLabel}, but this remains planning-only until the live execution inputs are upgraded.`,
|
|
)
|
|
} else {
|
|
console.log(
|
|
` Executable as modeled: start with ${fmtAmount(params.x, params.quoteDecimals, params.quoteAsset)} using ${simulation.strategyLabel}. It retains ${fmtAmount(simulation.retainedAfterGas, params.quoteDecimals, params.quoteAsset)} after gas${deploymentPlan ? ` and deploys ${fmtAmount(deploymentPlan.deployedTotal, params.quoteDecimals, params.quoteAsset)} into target pools` : ''}.`,
|
|
)
|
|
}
|
|
if (canarySizedPass) {
|
|
console.log(
|
|
` This is still canary-sized, not a meaningful public depth program yet: retained quote is below ${fmtAmount(canaryThreshold, params.quoteDecimals, params.quoteAsset)}.`,
|
|
)
|
|
}
|
|
} else {
|
|
console.log(
|
|
` Do not execute this tranche as modeled. Failing guards=${simulation.guardFailures.join(', ')}.`,
|
|
)
|
|
if (depthPlan && depthPlan.factor > 1) {
|
|
console.log(
|
|
` Closest structural fix: deepen the source pool by about ${fmt(depthPlan.factor)}x before retrying this exact tranche.`,
|
|
)
|
|
} else if (simulation.guardFailures.includes('post_trade_dev')) {
|
|
console.log(
|
|
' Current external assumptions plus current public-pool depth are not enough; improve venue economics, lower size, or seed depth first.',
|
|
)
|
|
}
|
|
}
|
|
console.log('')
|
|
console.log('Methodologies to increase depth')
|
|
methods.forEach((method, index) => {
|
|
console.log(` ${index + 1}. ${method}`)
|
|
})
|
|
console.log('')
|
|
}
|
|
|
|
function isMaterialPublicCandidate(candidate) {
|
|
if (!candidate || candidate.simulation.guardFailures.length !== 0) return false
|
|
if (candidate.simulation.deploymentPlan) {
|
|
return candidate.simulation.deploymentPlan.deployedTotal > 0
|
|
}
|
|
return candidate.simulation.retainedAfterGas >= humanToRaw(1, candidate.params.quoteDecimals)
|
|
}
|
|
|
|
function printFullLoopDryRun(params) {
|
|
const {
|
|
B,
|
|
Q,
|
|
x,
|
|
lpFeeBps,
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
externalEntryFeeBps,
|
|
externalExitFeeBps,
|
|
quoteAsset,
|
|
baseAsset,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
guardConfig,
|
|
flashProviderCap,
|
|
flashProviderName,
|
|
flashFeeSource,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
quoteSurface,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
livePoolFeeBps,
|
|
executionGradeRequired,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset,
|
|
atomicDestinationDecimals,
|
|
atomicCorridorLabel,
|
|
} = params
|
|
const simulation = simulateFullLoopDryRun(params)
|
|
const executionBlocks = listExecutionReadinessBlocks({
|
|
strategyLabel: params.loopStrategy,
|
|
quoteSurface,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
gasReserveMode: guardConfig.gasReserveMode,
|
|
maxPostTradeDeviationBps: guardConfig.maxPostTradeDeviationBps,
|
|
})
|
|
const executionClass = classifyExecutionReadiness({
|
|
strategyLabel: params.loopStrategy,
|
|
quoteSurface,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
gasReserveMode: guardConfig.gasReserveMode,
|
|
maxPostTradeDeviationBps: guardConfig.maxPostTradeDeviationBps,
|
|
})
|
|
const modeledStatusLabel = executionClass === 'execution-ready' ? 'status' : 'modeled status'
|
|
const {
|
|
strategyLabel,
|
|
effectiveBaseReserve,
|
|
effectiveQuoteReserve,
|
|
strategyQuote,
|
|
repay,
|
|
retainedBeforeGas,
|
|
retainedAfterGas,
|
|
endBaseReserve,
|
|
endQuoteReserve,
|
|
postTradeMarginalPrice,
|
|
postTradeDeviationBps,
|
|
postTradeDeviationAbsBps,
|
|
gasReserveSummary,
|
|
providerCapOk,
|
|
effectiveFlashFeeBps,
|
|
deviationOk,
|
|
deploymentPlan,
|
|
retainedFloorActive,
|
|
retainedFloorOk,
|
|
guardFailures,
|
|
leg,
|
|
availableForRepay,
|
|
step2MetricA,
|
|
step2MetricB,
|
|
step2MetricC,
|
|
step3MetricA,
|
|
step3MetricB,
|
|
strategyNote,
|
|
atomicBaseRequested,
|
|
atomicBaseReserved,
|
|
atomicBridgeShortfall,
|
|
atomicDestinationFulfilled,
|
|
unwindBaseAmount,
|
|
} = simulation
|
|
const atomicBridgeScan =
|
|
params.scanAtomicBridgeMax && strategyLabel === 'quote-push'
|
|
? findMaxAtomicBridgeReservation(params, leg?.baseOut ?? null)
|
|
: null
|
|
|
|
console.log('Full loop dry-run — flexible flash-backed PMM loop, repay, and retained quote deployment\n')
|
|
console.log(` strategy=${strategyLabel}`)
|
|
console.log(` execution class=${executionClass}`)
|
|
if (quoteSurface) {
|
|
console.log(` pool quote surface=${quoteSurface.surface} (${quoteSurface.source})`)
|
|
}
|
|
console.log(` flash provider=${flashProviderName}`)
|
|
console.log(
|
|
` source PMM reserves: B=${fmtAmount(B, baseDecimals, baseAsset)}, Q=${fmtAmount(Q, quoteDecimals, quoteAsset)}`,
|
|
)
|
|
if (ownedBaseAdd > 0) {
|
|
console.log(
|
|
` owned base add to source pool=${fmtAmount(ownedBaseAdd, baseDecimals, baseAsset)} (effective source B=${fmtAmount(effectiveBaseReserve, baseDecimals, baseAsset)})`,
|
|
)
|
|
}
|
|
console.log(` flash borrow size=${fmtAmount(x, quoteDecimals, quoteAsset)}`)
|
|
if (flashTopupQuote > 0) {
|
|
console.log(
|
|
` temporary flash quote top-up to source pool=${fmtAmount(flashTopupQuote, quoteDecimals, quoteAsset)} (effective source Q=${fmtAmount(effectiveQuoteReserve, quoteDecimals, quoteAsset)})`,
|
|
)
|
|
}
|
|
console.log(` strategy quote budget=${fmtAmount(strategyQuote, quoteDecimals, quoteAsset)}`)
|
|
console.log(
|
|
` pool fee=${fmt(livePoolFeeBps ?? lpFeeBps)} bps flash fee=${fmt(effectiveFlashFeeBps)} bps external entry fee=${fmt(externalEntryFeeBps)} bps external exit fee=${fmt(externalExitFeeBps)} bps`,
|
|
)
|
|
if (flashFeeSource) {
|
|
console.log(` flash fee source=${flashFeeSource}`)
|
|
}
|
|
console.log(` external entry source=${externalEntrySource ?? 'n/a'}`)
|
|
console.log(` external exit source=${externalExitSource ?? 'n/a'}`)
|
|
if (atomicBridgeBaseAmount > 0) {
|
|
console.log(
|
|
` atomic corridor reservation=${fmtAmount(atomicBridgeBaseAmount, baseDecimals, baseAsset)}${atomicCorridorLabel ? ` via ${atomicCorridorLabel}` : ''}`,
|
|
)
|
|
console.log(
|
|
` atomic destination fulfillment target=${fmtAmount(
|
|
atomicDestinationAmount ?? convertRawAmount(atomicBridgeBaseAmount, baseDecimals, atomicDestinationDecimals),
|
|
atomicDestinationDecimals,
|
|
atomicDestinationAsset,
|
|
)}`,
|
|
)
|
|
}
|
|
console.log(` oracle price=${fmt(guardConfig.oraclePrice)} ${quoteAsset} per ${baseAsset}`)
|
|
if (flashProviderCap != null) {
|
|
console.log(
|
|
` flash source cap=${fmtAmount(flashProviderCap, quoteDecimals, quoteAsset)} (${providerCapOk ? 'pass' : 'exceeds cap'})`,
|
|
)
|
|
} else {
|
|
console.log(' flash source cap=not checked (set --flash-provider-cap to enforce borrow ceiling)')
|
|
}
|
|
if (guardConfig.gasReserveQuote > 0) {
|
|
console.log(
|
|
` native gas reserve=${fmt(guardConfig.gasReserveNative)} native = ${fmt(guardConfig.gasReserveQuoteHuman)} ${quoteAsset} (${gasReserveSummary})`,
|
|
)
|
|
}
|
|
if (executionClass !== 'execution-ready') {
|
|
console.log(
|
|
' warning: this output is planning-only until the tool has the required live pool quote surface, live external quote source(s), measured or live-estimated gas, and an explicit post-trade deviation cap.',
|
|
)
|
|
}
|
|
if (retainedFloorActive) {
|
|
console.log(` retained ${quoteAsset} floor after gas=${fmtAmount(guardConfig.minRetainedUsdc, quoteDecimals, quoteAsset)}`)
|
|
}
|
|
if (guardConfig.maxPostTradeDeviationBps != null) {
|
|
console.log(` max post-trade deviation=${fmt(guardConfig.maxPostTradeDeviationBps)} bps`)
|
|
}
|
|
console.log('')
|
|
console.log('step\tmetric\tvalue')
|
|
if (ownedBaseAdd > 0) {
|
|
console.log(`0\towned_base_added_to_pool\t${fmtAmount(ownedBaseAdd, baseDecimals, baseAsset)}`)
|
|
console.log(`0\teffective_source_base_reserve\t${fmtAmount(effectiveBaseReserve, baseDecimals, baseAsset)}`)
|
|
}
|
|
console.log(`1\tflash_quote_borrowed\t${fmtAmount(x, quoteDecimals, quoteAsset)}`)
|
|
if (flashTopupQuote > 0) {
|
|
console.log(`1\tflash_quote_temp_pool_topup\t${fmtAmount(flashTopupQuote, quoteDecimals, quoteAsset)}`)
|
|
console.log(`1\tflash_quote_left_for_strategy\t${fmtAmount(strategyQuote, quoteDecimals, quoteAsset)}`)
|
|
}
|
|
console.log(step2MetricA.join('\t'))
|
|
console.log(step2MetricB.join('\t'))
|
|
console.log(step2MetricC.join('\t'))
|
|
if (atomicBaseRequested > 0) {
|
|
console.log(`3\tatomic_base_requested\t${fmtAmount(atomicBaseRequested, baseDecimals, baseAsset)}`)
|
|
console.log(`3\tatomic_base_reserved\t${fmtAmount(atomicBaseReserved, baseDecimals, baseAsset)}`)
|
|
console.log(
|
|
`3\tatomic_destination_fulfilled\t${fmtAmount(atomicDestinationFulfilled, atomicDestinationDecimals, atomicDestinationAsset)}`,
|
|
)
|
|
console.log(`3\tbase_remaining_for_unwind\t${fmtAmount(unwindBaseAmount, baseDecimals, baseAsset)}`)
|
|
if (atomicBridgeShortfall > 0) {
|
|
console.log(`3\tatomic_bridge_shortfall\t${fmtAmount(atomicBridgeShortfall, baseDecimals, baseAsset)}`)
|
|
}
|
|
}
|
|
console.log(step3MetricA.join('\t'))
|
|
console.log(step3MetricB.join('\t'))
|
|
if (strategyLabel === 'quote-push') {
|
|
console.log(`3\texternal_quote_proceeds\t${fmtAmount(availableForRepay, quoteDecimals, quoteAsset)}`)
|
|
} else {
|
|
console.log(`3\tpmm_vwap_quote_per_base\t${fmt(leg.vwapQuotePerBase)}`)
|
|
console.log(`3\tquote_available_for_repay\t${fmtAmount(availableForRepay, quoteDecimals, quoteAsset)}`)
|
|
}
|
|
console.log(`4\tflash_fee_amount\t${fmtAmount(repay - x, quoteDecimals, quoteAsset)}`)
|
|
console.log(`4\tflash_repay\t${fmtAmount(repay, quoteDecimals, quoteAsset)}`)
|
|
console.log(`4\tretained_before_gas\t${fmtAmount(retainedBeforeGas, quoteDecimals, quoteAsset)}`)
|
|
console.log(`5\tgas_reserve_quote\t${fmtAmount(guardConfig.gasReserveQuote, quoteDecimals, quoteAsset)}`)
|
|
console.log(`5\tretained_after_gas\t${fmtAmount(retainedAfterGas, quoteDecimals, quoteAsset)}`)
|
|
console.log(`6\tsource_pool_base_delta\t${fmtAmount(endBaseReserve - B, baseDecimals, baseAsset)}`)
|
|
console.log(`6\tsource_pool_quote_delta\t${fmtAmount(endQuoteReserve - Q, quoteDecimals, quoteAsset)}`)
|
|
console.log(`6\tpost_trade_marginal_price\t${fmt(postTradeMarginalPrice)}`)
|
|
console.log(`6\tpost_trade_dev_bps\t${fmt(postTradeDeviationBps)}`)
|
|
console.log('')
|
|
console.log(
|
|
` ${modeledStatusLabel}=${guardFailures.length === 0 ? 'pass' : 'fail'}${guardFailures.length === 0 ? '' : ` (${guardFailures.join('+')})`}`,
|
|
)
|
|
console.log(
|
|
` repay check=${availableForRepay >= repay ? 'pass' : 'fail'} (${fmtAmount(availableForRepay, quoteDecimals, quoteAsset)} proceeds vs ${fmtAmount(repay, quoteDecimals, quoteAsset)} repay)`,
|
|
)
|
|
console.log(
|
|
` retained after gas=${retainedAfterGas >= 0 ? 'pass' : 'fail'} (${fmtAmount(retainedAfterGas, quoteDecimals, quoteAsset)})`,
|
|
)
|
|
if (retainedFloorActive) {
|
|
console.log(
|
|
` retained floor after gas=${retainedFloorOk ? 'pass' : 'fail'} (${fmtAmount(retainedAfterGas, quoteDecimals, quoteAsset)} vs floor ${fmtAmount(
|
|
guardConfig.minRetainedUsdc,
|
|
quoteDecimals,
|
|
quoteAsset,
|
|
)})`,
|
|
)
|
|
}
|
|
if (deviationOk != null) {
|
|
console.log(
|
|
` post-trade deviation guard=${deviationOk ? 'pass' : 'fail'} (${fmt(postTradeDeviationAbsBps)} bps abs)`,
|
|
)
|
|
}
|
|
console.log(
|
|
` source pool end state: B=${fmtAmount(endBaseReserve, baseDecimals, baseAsset)}, Q=${fmtAmount(endQuoteReserve, quoteDecimals, quoteAsset)}`,
|
|
)
|
|
console.log(` source pool base deepening=${endBaseReserve > B ? 'yes' : 'no'} quote deepening=${endQuoteReserve > Q ? 'yes' : 'no'}`)
|
|
console.log(strategyNote)
|
|
console.log('')
|
|
if (atomicBridgeScan) {
|
|
console.log('Atomic bridge reservation scan')
|
|
console.log(
|
|
` PMM-acquired base available=${fmtAmount(atomicBridgeScan.maxBaseAvailable, baseDecimals, baseAsset)}`,
|
|
)
|
|
console.log(
|
|
` max reservation with positive retained-after-gas=${fmtAmount(
|
|
atomicBridgeScan.positiveAfterGas.amount,
|
|
baseDecimals,
|
|
baseAsset,
|
|
)}`,
|
|
)
|
|
console.log(
|
|
` retained_after_gas=${fmtAmount(
|
|
atomicBridgeScan.positiveAfterGas.simulation.retainedAfterGas,
|
|
quoteDecimals,
|
|
quoteAsset,
|
|
)}`,
|
|
)
|
|
console.log(
|
|
` max reservation with repay-only pass=${fmtAmount(
|
|
atomicBridgeScan.repayOnly.amount,
|
|
baseDecimals,
|
|
baseAsset,
|
|
)}`,
|
|
)
|
|
console.log(
|
|
` retained_after_gas=${fmtAmount(
|
|
atomicBridgeScan.repayOnly.simulation.retainedAfterGas,
|
|
quoteDecimals,
|
|
quoteAsset,
|
|
)}`,
|
|
)
|
|
console.log('')
|
|
}
|
|
if (executionGradeRequired && executionClass !== 'execution-ready') {
|
|
console.log('Execution-grade gate')
|
|
console.log(' status=blocked')
|
|
console.log(` reason=missing ${executionBlocks.join(', ')}`)
|
|
console.log('')
|
|
return
|
|
}
|
|
printQuoteDeploymentPlan({
|
|
retainedQuote: retainedAfterGas,
|
|
quoteAsset,
|
|
quoteDecimals,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
})
|
|
printFullLoopRecommendation(params, simulation, {
|
|
executionClass,
|
|
executionGradeRequired,
|
|
})
|
|
}
|
|
|
|
function strategyListForPublicSweep(loopStrategy) {
|
|
return loopStrategy === 'both' ? ['quote-push', 'base-unwind'] : [loopStrategy]
|
|
}
|
|
|
|
function defaultPublicSweepSizes(quoteDecimals) {
|
|
return [0.1, 0.25, 0.5, 1, 2, 4].map((amount) => humanToRaw(amount, quoteDecimals))
|
|
}
|
|
|
|
function resolvePublicFlashSource({
|
|
publicFlashSource,
|
|
quoteAddress,
|
|
flashProviderCap,
|
|
flashProviderName,
|
|
flashFeeBps,
|
|
}) {
|
|
if (publicFlashSource === 'manual') {
|
|
return {
|
|
providerKey: 'manual',
|
|
providerName: flashProviderName,
|
|
providerAddress: null,
|
|
capRaw: flashProviderCap,
|
|
feeBps: flashFeeBps,
|
|
feeRaw: null,
|
|
source: 'manual CLI/env flash source',
|
|
}
|
|
}
|
|
|
|
const liveSource =
|
|
publicFlashSource === 'balancer'
|
|
? tryReadMainnetBalancerFlashSource(quoteAddress)
|
|
: tryReadMainnetAaveFlashSource(quoteAddress)
|
|
if (!liveSource) return null
|
|
|
|
return {
|
|
...liveSource,
|
|
capRaw:
|
|
liveSource.capRaw != null && flashProviderCap != null
|
|
? Math.min(liveSource.capRaw, flashProviderCap)
|
|
: (flashProviderCap ?? liveSource.capRaw),
|
|
}
|
|
}
|
|
|
|
function buildPublicFlashTerms(flashSource, amount, fallbackFlashFeeBps) {
|
|
const effectiveFlashFeeBps = flashSource?.feeBps ?? fallbackFlashFeeBps
|
|
return {
|
|
flashProviderCap: flashSource?.capRaw ?? null,
|
|
flashProviderName: flashSource?.providerName ?? 'flash provider',
|
|
flashFeeAmount: null,
|
|
flashFeeBps: effectiveFlashFeeBps,
|
|
flashFeeSource:
|
|
flashSource?.source ?? `manual flash fee bps (${fmt(effectiveFlashFeeBps)})`,
|
|
effectiveFlashFeeBps,
|
|
}
|
|
}
|
|
|
|
function buildPublicLiveSweepCandidate({
|
|
pool,
|
|
reserves,
|
|
livePoolFeeBps,
|
|
quoteSurface,
|
|
x,
|
|
strategy,
|
|
ownedBaseAdd,
|
|
flashSource,
|
|
fallbackFlashFeeBps,
|
|
baseDecimals,
|
|
quoteDecimals,
|
|
guardConfig,
|
|
flashTopupQuote,
|
|
externalEntryPrice,
|
|
externalExitPrice,
|
|
externalEntryFeeBps,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset,
|
|
atomicDestinationDecimals,
|
|
atomicCorridorLabel,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
fromAddress,
|
|
}) {
|
|
const estimatedGasQuote = deriveEstimatedGasQuoteForMainnetStrategy({
|
|
strategy,
|
|
poolAddress: pool.poolAddress,
|
|
baseAddress: pool.baseAddress,
|
|
quoteAddress: pool.quoteAddress,
|
|
amountInQuoteRaw: x,
|
|
externalEntryPrice,
|
|
externalEntryFeeBps,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
nativeTokenPrice: guardConfig.nativeTokenPrice,
|
|
fromAddress,
|
|
})
|
|
const candidateGuardConfig =
|
|
guardConfig.gasReserveMode === 'modeled_native' && estimatedGasQuote
|
|
? buildInventoryLoopGuardConfig({
|
|
minRetainedUsdc: guardConfig.minRetainedUsdc,
|
|
gasTxCount: null,
|
|
gasPerTx: null,
|
|
maxFeeGwei: null,
|
|
nativeTokenPrice: guardConfig.nativeTokenPrice,
|
|
quoteDecimals,
|
|
oraclePrice: guardConfig.oraclePrice,
|
|
maxPostTradeDeviationBps: guardConfig.maxPostTradeDeviationBps,
|
|
maxCwBurnFraction: guardConfig.maxCwBurnFraction,
|
|
cooldownBlocks: guardConfig.cooldownBlocks,
|
|
measuredGasQuote: null,
|
|
measuredGasSource: null,
|
|
estimatedGasQuote: estimatedGasQuote.quoteCost,
|
|
estimatedGasSource: estimatedGasQuote.source,
|
|
})
|
|
: guardConfig
|
|
const flashTerms = buildPublicFlashTerms(flashSource, x, fallbackFlashFeeBps)
|
|
const params = {
|
|
B: reserves.baseReserve,
|
|
Q: reserves.quoteReserve,
|
|
x,
|
|
lpFeeBps: livePoolFeeBps ?? pool.feeBps ?? 10,
|
|
flashFeeBps: flashTerms.flashFeeBps,
|
|
loopStrategy: strategy,
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
externalEntryPrice,
|
|
externalExitPrice,
|
|
externalEntryFeeBps,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
quoteAsset: pool.quote,
|
|
baseAsset: pool.base,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
guardConfig: candidateGuardConfig,
|
|
flashProviderCap: flashTerms.flashProviderCap,
|
|
flashProviderName: flashTerms.flashProviderName,
|
|
flashFeeAmount: flashTerms.flashFeeAmount,
|
|
flashFeeSource: flashTerms.flashFeeSource,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset,
|
|
atomicDestinationDecimals,
|
|
atomicCorridorLabel,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
}
|
|
const simulation = simulateFullLoopDryRun(params)
|
|
const depthPlan =
|
|
simulation.guardFailures.length === 0 ? null : findMinimumUniformDepthMultiplierForPass(params)
|
|
return {
|
|
pool,
|
|
x,
|
|
strategy,
|
|
flashSource,
|
|
quoteSurface,
|
|
livePoolFeeBps,
|
|
params,
|
|
simulation,
|
|
depthPlan,
|
|
flashProviderCap: flashTerms.flashProviderCap,
|
|
executionClass: classifyExecutionReadiness({
|
|
strategyLabel: strategy,
|
|
quoteSurface,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
gasReserveMode: candidateGuardConfig.gasReserveMode,
|
|
maxPostTradeDeviationBps: candidateGuardConfig.maxPostTradeDeviationBps,
|
|
}),
|
|
}
|
|
}
|
|
|
|
function printPublicLiveSweep({
|
|
scanStr,
|
|
loopStrategy,
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
flashProviderCap,
|
|
flashProviderName,
|
|
flashFeeBps,
|
|
externalEntryPrice,
|
|
externalExitPrice,
|
|
externalEntryFeeBps,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset,
|
|
atomicDestinationDecimals,
|
|
atomicCorridorLabel,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
guardConfig,
|
|
publicFlashSource,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
executionGradeRequired,
|
|
}) {
|
|
const pools = loadLiveMainnetPublicUsdPools()
|
|
if (pools.length === 0) {
|
|
console.error('Error: no live mainnet cWUSD*/USD* PMM pools were found in deployment-status.json')
|
|
process.exit(1)
|
|
}
|
|
|
|
const rpcUrl = rpcUrlForChain(1)
|
|
if (!rpcUrl) {
|
|
console.error('Error: --public-live-sweep requires ETHEREUM_MAINNET_RPC')
|
|
process.exit(1)
|
|
}
|
|
|
|
const strategies = strategyListForPublicSweep(loopStrategy)
|
|
const sweepRows = []
|
|
const fromAddress = process.env.DEPLOYER_ADDRESS ?? '0x4A666F96fC8764181194447A7dFdb7d471b301C8'
|
|
|
|
for (const pool of pools) {
|
|
const reserves = tryReadPoolReservesViaCast(1, pool.poolAddress)
|
|
if (!reserves) {
|
|
sweepRows.push({
|
|
pool,
|
|
strategy: 'n/a',
|
|
best: null,
|
|
error: `could not read live reserves for ${pool.poolAddress}`,
|
|
})
|
|
continue
|
|
}
|
|
|
|
const baseDecimals = tryReadTokenDecimalsViaCast(1, pool.baseAddress) ?? 6
|
|
const quoteDecimals = tryReadTokenDecimalsViaCast(1, pool.quoteAddress) ?? 6
|
|
const scanSizes = scanStr
|
|
? scanStr.split(',').map((size) => parseNum(size.trim()))
|
|
: defaultPublicSweepSizes(quoteDecimals)
|
|
const flashSource = resolvePublicFlashSource({
|
|
publicFlashSource,
|
|
quoteAddress: pool.quoteAddress,
|
|
flashProviderCap,
|
|
flashProviderName,
|
|
flashFeeBps,
|
|
})
|
|
const livePoolFeeBps = tryReadPoolFeeBpsViaCast(1, pool.poolAddress)
|
|
const quoteSurface = tryProbePoolQuoteSurfaceViaCast(
|
|
1,
|
|
pool.poolAddress,
|
|
fromAddress,
|
|
)
|
|
|
|
for (const strategy of strategies) {
|
|
const candidates = scanSizes.map((x) =>
|
|
buildPublicLiveSweepCandidate({
|
|
pool,
|
|
reserves,
|
|
livePoolFeeBps,
|
|
quoteSurface,
|
|
x,
|
|
strategy,
|
|
ownedBaseAdd,
|
|
flashSource,
|
|
fallbackFlashFeeBps: flashFeeBps,
|
|
baseDecimals,
|
|
quoteDecimals,
|
|
guardConfig,
|
|
flashTopupQuote,
|
|
externalEntryPrice,
|
|
externalExitPrice,
|
|
externalEntryFeeBps,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset,
|
|
atomicDestinationDecimals,
|
|
atomicCorridorLabel,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
fromAddress,
|
|
}),
|
|
)
|
|
const best = [...candidates].sort(compareFullLoopCandidates)[0] ?? null
|
|
sweepRows.push({
|
|
pool,
|
|
strategy,
|
|
best,
|
|
candidates,
|
|
reserves,
|
|
flashSource,
|
|
quoteSurface,
|
|
livePoolFeeBps,
|
|
error: flashSource == null ? `could not resolve ${publicFlashSource} flash source for ${pool.quote}` : null,
|
|
})
|
|
}
|
|
}
|
|
|
|
console.log('Public live sweep — mainnet cWUSD*/USD* PMM pools\n')
|
|
console.log(' chain=Ethereum mainnet')
|
|
console.log(` flash source mode=${publicFlashSource}`)
|
|
console.log(` strategies=${strategies.join(', ')}`)
|
|
console.log(
|
|
` scan sizes=${(scanStr ? scanStr.split(',').map((size) => parseNum(size.trim())) : defaultPublicSweepSizes(6))
|
|
.map((size) => fmt(size))
|
|
.join(', ')} raw quote units`,
|
|
)
|
|
if (guardConfig.gasReserveQuote > 0) {
|
|
console.log(
|
|
` gas reserve=${fmt(guardConfig.gasReserveQuote)} raw quote units (${summarizeGasReserve(guardConfig)})`,
|
|
)
|
|
}
|
|
if (guardConfig.maxPostTradeDeviationBps != null) {
|
|
console.log(` max post-trade deviation=${fmt(guardConfig.maxPostTradeDeviationBps)} bps`)
|
|
}
|
|
console.log('')
|
|
console.log(
|
|
'pool\tstrategy\texecution_class\tquote_surface\tfee_bps\tbest_x\tstatus\tguards\tretained_after_gas\tpost_trade_dev_bps\tfunded_steps\tdepth_needed',
|
|
)
|
|
|
|
for (const row of sweepRows) {
|
|
if (row.error || !row.best) {
|
|
console.log(
|
|
[
|
|
row.pool?.poolAddress ?? 'unknown',
|
|
row.strategy,
|
|
'blocked',
|
|
'unknown',
|
|
'n/a',
|
|
'n/a',
|
|
'error',
|
|
row.error ?? 'n/a',
|
|
'n/a',
|
|
'n/a',
|
|
'n/a',
|
|
'n/a',
|
|
].join('\t'),
|
|
)
|
|
continue
|
|
}
|
|
const { best } = row
|
|
const fundedSteps = best.simulation.deploymentPlan
|
|
? `${best.simulation.deploymentPlan.fullyFundedCount}/${best.simulation.deploymentPlan.rows.length}`
|
|
: 'n/a'
|
|
const depthNeeded =
|
|
best.depthPlan && best.depthPlan.factor > 1 ? `${fmt(best.depthPlan.factor)}x` : 'pass'
|
|
const executionClass = row.best.executionClass
|
|
const rowModeledPass = best.simulation.guardFailures.length === 0
|
|
const rowStatus =
|
|
executionGradeRequired && executionClass !== 'execution-ready'
|
|
? 'blocked'
|
|
: rowModeledPass
|
|
? executionClass === 'execution-ready'
|
|
? 'pass'
|
|
: 'planning-pass'
|
|
: 'closest'
|
|
const rowGuards =
|
|
executionGradeRequired && executionClass !== 'execution-ready'
|
|
? 'execution_gate'
|
|
: rowModeledPass
|
|
? executionClass === 'execution-ready'
|
|
? 'pass'
|
|
: 'modeled_only'
|
|
: best.simulation.guardFailures.join('+')
|
|
console.log(
|
|
[
|
|
`${row.pool.base}/${row.pool.quote}`,
|
|
row.strategy,
|
|
executionClass,
|
|
row.quoteSurface?.surface ?? 'unknown',
|
|
fmt(row.livePoolFeeBps ?? row.pool.feeBps ?? NaN),
|
|
fmtAmount(best.x, best.params.quoteDecimals, best.params.quoteAsset),
|
|
rowStatus,
|
|
rowGuards,
|
|
fmtAmount(
|
|
best.simulation.retainedAfterGas,
|
|
best.params.quoteDecimals,
|
|
best.params.quoteAsset,
|
|
),
|
|
fmt(best.simulation.postTradeDeviationBps),
|
|
fundedSteps,
|
|
depthNeeded,
|
|
].join('\t'),
|
|
)
|
|
}
|
|
|
|
const viable = sweepRows
|
|
.map((row) => row.best)
|
|
.filter(
|
|
(candidate) =>
|
|
candidate &&
|
|
candidate.simulation.guardFailures.length === 0 &&
|
|
(!executionGradeRequired ||
|
|
candidate.executionClass === 'execution-ready'),
|
|
)
|
|
.sort(compareFullLoopCandidates)
|
|
const materiallyUseful = viable.filter(isMaterialPublicCandidate)
|
|
const closest = sweepRows
|
|
.map((row) => row.best)
|
|
.filter(Boolean)
|
|
.sort(compareFullLoopCandidates)[0]
|
|
const failedCandidates = sweepRows
|
|
.flatMap((row) => row.candidates ?? [])
|
|
.filter((candidate) => candidate.simulation.guardFailures.length > 0)
|
|
const preferredFailedCandidates = failedCandidates.filter(
|
|
(candidate) => candidate.strategy === 'quote-push',
|
|
)
|
|
const closestFail =
|
|
[...(preferredFailedCandidates.length > 0 ? preferredFailedCandidates : failedCandidates)].sort(
|
|
compareFullLoopCandidates,
|
|
)[0]
|
|
|
|
console.log('')
|
|
console.log('Overall recommendation')
|
|
if (materiallyUseful.length > 0) {
|
|
const winner = materiallyUseful[0]
|
|
const winnerExecutionClass = winner.executionClass
|
|
console.log(
|
|
` Best current live public candidate: ${winner.pool.base}/${winner.pool.quote} via ${winner.strategy} at ${fmtAmount(winner.x, winner.params.quoteDecimals, winner.params.quoteAsset)} using ${winner.flashSource?.providerName ?? 'manual flash source'}.`,
|
|
)
|
|
printFullLoopRecommendation(winner.params, winner.simulation, {
|
|
executionClass: winnerExecutionClass,
|
|
executionGradeRequired,
|
|
})
|
|
} else if (viable.length > 0) {
|
|
const canary = viable[0]
|
|
const recommendationCandidate = closestFail ?? canary
|
|
const recommendationExecutionClass = recommendationCandidate.executionClass
|
|
console.log(
|
|
` Only canary-sized pass candidates exist right now. Best technical pass: ${canary.pool.base}/${canary.pool.quote} via ${canary.strategy} at ${fmtAmount(canary.x, canary.params.quoteDecimals, canary.params.quoteAsset)}, but it does not retain enough quote to count as a meaningful public-liquidity program.`,
|
|
)
|
|
printFullLoopRecommendation(recommendationCandidate.params, recommendationCandidate.simulation, {
|
|
executionClass: recommendationExecutionClass,
|
|
executionGradeRequired,
|
|
})
|
|
} else if (closest) {
|
|
const closestExecutionClass = closest.executionClass
|
|
console.log(
|
|
` No current live public pool passes the active guard set. Closest candidate: ${closest.pool.base}/${closest.pool.quote} via ${closest.strategy} at ${fmtAmount(closest.x, closest.params.quoteDecimals, closest.params.quoteAsset)}.`,
|
|
)
|
|
printFullLoopRecommendation(closest.params, closest.simulation, {
|
|
executionClass: closestExecutionClass,
|
|
executionGradeRequired,
|
|
})
|
|
} else {
|
|
console.log(' No live public sweep candidates could be evaluated.')
|
|
console.log('')
|
|
}
|
|
}
|
|
|
|
function buildInventoryLoopCandidates(
|
|
B,
|
|
Q,
|
|
scanStr,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
inventoryBase,
|
|
targetFlashBorrow,
|
|
guardConfig,
|
|
flashAddToPool,
|
|
) {
|
|
const sizes = scanStr
|
|
? scanStr.split(',').map((s) => parseNum(s.trim()))
|
|
: [50_000, 100_000, 150_000, 200_000, 250_000]
|
|
|
|
return sizes.map((grossBaseIn) => {
|
|
const effectiveQuoteReserve =
|
|
flashAddToPool && targetFlashBorrow != null ? Q + targetFlashBorrow : Q
|
|
const leg = baseInToQuoteOut(B, effectiveQuoteReserve, grossBaseIn, lpFeeBps)
|
|
const endBaseReserve = B + leg.deltaBaseNet
|
|
const endQuoteReserve = Math.max(0, effectiveQuoteReserve - leg.quoteOut)
|
|
const postTradeMarginalPrice = marginalQuotePerBase(endBaseReserve, endQuoteReserve)
|
|
const postTradeDeviationBps = deviationBps(postTradeMarginalPrice, guardConfig.oraclePrice)
|
|
const postTradeDeviationAbsBps = Math.abs(postTradeDeviationBps)
|
|
const maxFlashNoLoss = leg.quoteOut / (1 + flashFeeBps / 10000)
|
|
const maxFlashKeepFloor = Math.max(
|
|
0,
|
|
(leg.quoteOut - guardConfig.requiredRetainedUsdc) / (1 + flashFeeBps / 10000),
|
|
)
|
|
const retainedNoFlash = leg.quoteOut
|
|
const floorSurplusNoFlash = retainedNoFlash - guardConfig.requiredRetainedUsdc
|
|
const retainedPerCw = retainedNoFlash / grossBaseIn
|
|
const inventoryOk = inventoryBase == null ? null : inventoryBase >= grossBaseIn
|
|
const inventoryBurnFraction =
|
|
inventoryBase == null || inventoryBase <= 0 ? null : grossBaseIn / inventoryBase
|
|
const postTradeDeviationOk =
|
|
guardConfig.maxPostTradeDeviationBps == null
|
|
? null
|
|
: postTradeDeviationAbsBps <= guardConfig.maxPostTradeDeviationBps
|
|
const cwBurnFractionOk =
|
|
guardConfig.maxCwBurnFraction == null
|
|
? null
|
|
: inventoryBurnFraction <= guardConfig.maxCwBurnFraction
|
|
const targetRepay = targetFlashBorrow == null ? null : repayUsdc(targetFlashBorrow, flashFeeBps)
|
|
const repayMargin = targetRepay == null ? null : leg.quoteOut - targetRepay
|
|
const retainedAfterTargetFlash = targetRepay == null ? null : leg.quoteOut - targetRepay
|
|
const retainedAfterTargetFlashPerCw =
|
|
retainedAfterTargetFlash == null ? null : retainedAfterTargetFlash / grossBaseIn
|
|
const floorMarginAfterTargetFlash =
|
|
retainedAfterTargetFlash == null ? null : retainedAfterTargetFlash - guardConfig.requiredRetainedUsdc
|
|
const poolBaseDelta = endBaseReserve - B
|
|
const poolQuoteDelta = endQuoteReserve - Q
|
|
const samePoolRepayPossible = targetRepay == null ? null : leg.quoteOut >= targetRepay
|
|
const samePoolQuoteEndsHigher = flashAddToPool ? endQuoteReserve > Q : null
|
|
const samePoolQuoteDeepeningWithPmmRepayPossible =
|
|
flashAddToPool && targetRepay != null ? samePoolRepayPossible && samePoolQuoteEndsHigher : null
|
|
|
|
const guardFailures = []
|
|
if (inventoryOk === false) guardFailures.push('inventory')
|
|
if (postTradeDeviationOk === false) guardFailures.push('post_trade_dev')
|
|
if (cwBurnFractionOk === false) guardFailures.push('cw_burn')
|
|
if (targetFlashBorrow != null) {
|
|
if (!(repayMargin >= 0)) guardFailures.push('repay')
|
|
if (!(floorMarginAfterTargetFlash >= 0)) guardFailures.push('retained_floor')
|
|
} else if (!(floorSurplusNoFlash >= 0)) {
|
|
guardFailures.push('retained_floor')
|
|
}
|
|
|
|
return {
|
|
grossBaseIn,
|
|
deltaBaseNet: leg.deltaBaseNet,
|
|
quoteOut: leg.quoteOut,
|
|
usdcPerCw: leg.vwapQuotePerBase,
|
|
endBaseReserve,
|
|
endQuoteReserve,
|
|
postTradeMarginalPrice,
|
|
postTradeDeviationBps,
|
|
postTradeDeviationAbsBps,
|
|
maxFlashNoLoss,
|
|
maxFlashKeepFloor,
|
|
retainedNoFlash,
|
|
floorSurplusNoFlash,
|
|
retainedPerCw,
|
|
inventoryOk,
|
|
inventoryBurnFraction,
|
|
postTradeDeviationOk,
|
|
cwBurnFractionOk,
|
|
targetRepay,
|
|
repayMargin,
|
|
retainedAfterTargetFlash,
|
|
retainedAfterTargetFlashPerCw,
|
|
floorMarginAfterTargetFlash,
|
|
effectiveQuoteReserve,
|
|
poolBaseDelta,
|
|
poolQuoteDelta,
|
|
samePoolRepayPossible,
|
|
samePoolQuoteEndsHigher,
|
|
samePoolQuoteDeepeningWithPmmRepayPossible,
|
|
guardFailures,
|
|
recommendationOk: guardFailures.length === 0,
|
|
}
|
|
})
|
|
}
|
|
|
|
function compareInventoryLoopCandidates(a, b, targetFlashBorrow, rankProfile) {
|
|
if (a.recommendationOk !== b.recommendationOk) {
|
|
return Number(b.recommendationOk) - Number(a.recommendationOk)
|
|
}
|
|
if (a.recommendationOk && b.recommendationOk) {
|
|
const eligibleCompare = compareRankProfileMetrics(
|
|
a,
|
|
b,
|
|
eligibleRankMetrics(targetFlashBorrow, rankProfile),
|
|
)
|
|
if (eligibleCompare !== 0) return eligibleCompare
|
|
}
|
|
if (a.guardFailures.length !== b.guardFailures.length) {
|
|
return a.guardFailures.length - b.guardFailures.length
|
|
}
|
|
if (targetFlashBorrow != null) {
|
|
if ((a.repayMargin >= 0) !== (b.repayMargin >= 0)) return Number(b.repayMargin >= 0) - Number(a.repayMargin >= 0)
|
|
if ((a.floorMarginAfterTargetFlash >= 0) !== (b.floorMarginAfterTargetFlash >= 0)) {
|
|
return Number(b.floorMarginAfterTargetFlash >= 0) - Number(a.floorMarginAfterTargetFlash >= 0)
|
|
}
|
|
if (a.repayMargin !== b.repayMargin) return b.repayMargin - a.repayMargin
|
|
if (a.floorMarginAfterTargetFlash !== b.floorMarginAfterTargetFlash) {
|
|
return b.floorMarginAfterTargetFlash - a.floorMarginAfterTargetFlash
|
|
}
|
|
} else {
|
|
if (a.maxFlashKeepFloor !== b.maxFlashKeepFloor) return b.maxFlashKeepFloor - a.maxFlashKeepFloor
|
|
if (a.floorSurplusNoFlash !== b.floorSurplusNoFlash) return b.floorSurplusNoFlash - a.floorSurplusNoFlash
|
|
}
|
|
if (a.retainedPerCw !== b.retainedPerCw) return b.retainedPerCw - a.retainedPerCw
|
|
return a.grossBaseIn - b.grossBaseIn
|
|
}
|
|
|
|
function printInventoryLoopSafetyScan(
|
|
B,
|
|
Q,
|
|
scanStr,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
inventoryBase,
|
|
targetFlashBorrow,
|
|
guardConfig,
|
|
flashAddToPool,
|
|
) {
|
|
const candidates = buildInventoryLoopCandidates(
|
|
B,
|
|
Q,
|
|
scanStr,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
inventoryBase,
|
|
targetFlashBorrow,
|
|
guardConfig,
|
|
flashAddToPool,
|
|
)
|
|
const eligibleCandidates = candidates.filter((candidate) => candidate.recommendationOk)
|
|
const gasReserveSummary = summarizeGasReserve(guardConfig)
|
|
const currentMarginalPrice = marginalQuotePerBase(B, Q)
|
|
const currentDeviation = deviationBps(currentMarginalPrice, guardConfig.oraclePrice)
|
|
|
|
console.log('Inventory-backed flash safety scan — sell cW base into PMM, borrow only what can be repaid\n')
|
|
console.log(` source reserves: B=${fmt(B)} cW base, Q=${fmt(Q)} USDC quote`)
|
|
console.log(` pool fee=${lpFeeBps} bps`)
|
|
console.log(` flash fee=${flashFeeBps} bps`)
|
|
console.log(` oracle price=${fmt(guardConfig.oraclePrice)} quote per cW base`)
|
|
console.log(` current marginal price=${fmt(currentMarginalPrice)} current deviation=${fmt(currentDeviation)} bps`)
|
|
console.log(` retained USDC floor=${fmtAmount(guardConfig.requiredRetainedUsdc, guardConfig.quoteDecimals, 'USDC')}`)
|
|
if (guardConfig.gasReserveQuote > 0) {
|
|
console.log(
|
|
` native gas reserve=${fmt(guardConfig.gasReserveNative)} native = ${fmt(guardConfig.gasReserveQuoteHuman)} USDC (${gasReserveSummary})`,
|
|
)
|
|
}
|
|
if (inventoryBase != null) console.log(` inventoryBase available=${fmt(inventoryBase)}`)
|
|
if (targetFlashBorrow != null) console.log(` target flash borrow=${fmt(targetFlashBorrow)} target repay=${fmt(repayUsdc(targetFlashBorrow, flashFeeBps))}`)
|
|
if (flashAddToPool && targetFlashBorrow != null) {
|
|
console.log(` flash loop mode=temporary quote top-up before PMM leg (effective Q=${fmt(Q + targetFlashBorrow)})`)
|
|
}
|
|
if (guardConfig.maxPostTradeDeviationBps != null) {
|
|
console.log(` max post-trade deviation=${fmt(guardConfig.maxPostTradeDeviationBps)} bps`)
|
|
}
|
|
if (guardConfig.maxCwBurnFraction != null) {
|
|
console.log(` max cW burn fraction=${fmt(guardConfig.maxCwBurnFraction)} of inventory-base`)
|
|
}
|
|
if (guardConfig.cooldownBlocks > 0) {
|
|
console.log(` cooldown after execution=${guardConfig.cooldownBlocks} blocks before next tranche`)
|
|
}
|
|
console.log('')
|
|
|
|
console.log(
|
|
'gross_cw_in\tquote_out\tUSDC_per_cw\tmax_flash_no_loss\tmax_flash_keep_floor\tpool_base_delta\tpool_quote_delta\tpost_trade_dev_bps\tcw_burn_frac\tguard_status',
|
|
)
|
|
|
|
let bestBorrow = null
|
|
let bestEfficiency = null
|
|
|
|
for (const candidate of candidates) {
|
|
if (
|
|
candidate.recommendationOk &&
|
|
(bestBorrow == null || candidate.maxFlashKeepFloor > bestBorrow.maxFlashKeepFloor)
|
|
) {
|
|
bestBorrow = candidate
|
|
}
|
|
if (
|
|
candidate.recommendationOk &&
|
|
(bestEfficiency == null || candidate.retainedPerCw > bestEfficiency.retainedPerCw)
|
|
) {
|
|
bestEfficiency = candidate
|
|
}
|
|
|
|
console.log(
|
|
[
|
|
fmt(candidate.grossBaseIn),
|
|
fmt(candidate.quoteOut),
|
|
fmt(candidate.usdcPerCw),
|
|
fmt(candidate.maxFlashNoLoss),
|
|
fmt(candidate.maxFlashKeepFloor),
|
|
fmt(candidate.poolBaseDelta),
|
|
fmt(candidate.poolQuoteDelta),
|
|
fmt(candidate.postTradeDeviationBps),
|
|
candidate.inventoryBurnFraction == null ? 'n/a' : fmt(candidate.inventoryBurnFraction),
|
|
formatGuardStatus(candidate),
|
|
].join('\t'),
|
|
)
|
|
}
|
|
|
|
console.log('')
|
|
if (bestBorrow) {
|
|
console.log(
|
|
` Max safe flash borrow while keeping the floor is highest at gross_cw_in=${fmt(bestBorrow.grossBaseIn)} → max_flash_keep_floor=${fmt(bestBorrow.maxFlashKeepFloor)}.`,
|
|
)
|
|
}
|
|
if (bestEfficiency) {
|
|
console.log(
|
|
` Best USDC retained per cW sacrificed is the smallest tested leg: gross_cw_in=${fmt(bestEfficiency.grossBaseIn)} → retained_per_cw=${fmt(bestEfficiency.retainedPerCw)}.`,
|
|
)
|
|
}
|
|
if (eligibleCandidates.length === 0) {
|
|
const closest = candidates[0]
|
|
console.log(
|
|
` No tranche passes the active guards. Top-ranked ineligible candidate gross_cw_in=${fmt(closest.grossBaseIn)} fails ${formatGuardStatus(closest)}.`,
|
|
)
|
|
}
|
|
if (guardConfig.cooldownBlocks > 0 && eligibleCandidates.length > 0) {
|
|
console.log(` Execute at most one eligible tranche per ${guardConfig.cooldownBlocks}-block window.`)
|
|
}
|
|
if (flashAddToPool && targetFlashBorrow != null) {
|
|
const anySamePoolQuoteDeepening = candidates.some(
|
|
(candidate) => candidate.samePoolQuoteDeepeningWithPmmRepayPossible,
|
|
)
|
|
if (!anySamePoolQuoteDeepening) {
|
|
console.log(
|
|
' Invariant: if the flash-borrowed quote is temporarily added to the same PMM and repaid from that same PMM quote-out leg, the pool cannot finish with a higher quote reserve Q. This loop deepens base B; permanent quote-side deepening requires outside proceeds.',
|
|
)
|
|
}
|
|
}
|
|
console.log(' Execution rule: repay flash first, then seed only the retained USDC remainder.')
|
|
console.log('')
|
|
}
|
|
|
|
function printInventoryLoopRanking(
|
|
B,
|
|
Q,
|
|
scanStr,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
inventoryBase,
|
|
targetFlashBorrow,
|
|
guardConfig,
|
|
rankProfile,
|
|
flashAddToPool,
|
|
) {
|
|
const candidates = buildInventoryLoopCandidates(
|
|
B,
|
|
Q,
|
|
scanStr,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
inventoryBase,
|
|
targetFlashBorrow,
|
|
guardConfig,
|
|
flashAddToPool,
|
|
).sort((a, b) => compareInventoryLoopCandidates(a, b, targetFlashBorrow, rankProfile))
|
|
const eligibleCandidates = candidates.filter((candidate) => candidate.recommendationOk)
|
|
const recommended = eligibleCandidates[0] ?? null
|
|
const runnerUp = eligibleCandidates[1] ?? null
|
|
const gasReserveSummary = summarizeGasReserve(guardConfig)
|
|
const currentMarginalPrice = marginalQuotePerBase(B, Q)
|
|
const currentDeviation = deviationBps(currentMarginalPrice, guardConfig.oraclePrice)
|
|
const profileSummary = rankProfileSummary(rankProfile, targetFlashBorrow)
|
|
const winningExplanation = explainWinningCandidate(recommended, runnerUp, rankProfile, targetFlashBorrow)
|
|
const efficiencyLabel = efficiencyMetricLabel(targetFlashBorrow)
|
|
|
|
console.log('Inventory-backed tranche ranking\n')
|
|
console.log(` source reserves: B=${fmt(B)} cW base, Q=${fmt(Q)} USDC quote`)
|
|
console.log(` pool fee=${lpFeeBps} bps`)
|
|
console.log(` flash fee=${flashFeeBps} bps`)
|
|
console.log(` oracle price=${fmt(guardConfig.oraclePrice)} quote per cW base`)
|
|
console.log(` current marginal price=${fmt(currentMarginalPrice)} current deviation=${fmt(currentDeviation)} bps`)
|
|
console.log(` retained USDC floor=${fmtAmount(guardConfig.requiredRetainedUsdc, guardConfig.quoteDecimals, 'USDC')}`)
|
|
if (guardConfig.gasReserveQuote > 0) {
|
|
console.log(
|
|
` native gas reserve=${fmt(guardConfig.gasReserveNative)} native = ${fmt(guardConfig.gasReserveQuoteHuman)} USDC (${gasReserveSummary})`,
|
|
)
|
|
}
|
|
if (inventoryBase != null) console.log(` inventoryBase available=${fmt(inventoryBase)}`)
|
|
if (targetFlashBorrow != null) {
|
|
console.log(` ranking against target flash borrow=${fmt(targetFlashBorrow)} repay=${fmt(repayUsdc(targetFlashBorrow, flashFeeBps))}`)
|
|
} else {
|
|
console.log(' ranking mode: eligible tranches are ordered by the selected rank profile')
|
|
}
|
|
if (flashAddToPool && targetFlashBorrow != null) {
|
|
console.log(` flash loop mode=temporary quote top-up before PMM leg (effective Q=${fmt(Q + targetFlashBorrow)})`)
|
|
}
|
|
console.log(` rank profile=${rankProfile}`)
|
|
console.log(` profile priorities=${profileSummary}`)
|
|
if (guardConfig.maxPostTradeDeviationBps != null) {
|
|
console.log(` max post-trade deviation=${fmt(guardConfig.maxPostTradeDeviationBps)} bps`)
|
|
}
|
|
if (guardConfig.maxCwBurnFraction != null) {
|
|
console.log(` max cW burn fraction=${fmt(guardConfig.maxCwBurnFraction)} of inventory-base`)
|
|
}
|
|
if (guardConfig.cooldownBlocks > 0) {
|
|
console.log(` cooldown after execution=${guardConfig.cooldownBlocks} blocks before next tranche`)
|
|
}
|
|
console.log('')
|
|
|
|
if (targetFlashBorrow != null) {
|
|
console.log(
|
|
`rank\tgross_cw_in\trepay_margin\tfloor_margin_after_repay\tpool_base_delta\tpool_quote_delta\tpost_trade_dev_bps\tcw_burn_frac\t${efficiencyLabel}\tguard_status`,
|
|
)
|
|
} else {
|
|
console.log(
|
|
`rank\tgross_cw_in\tmax_flash_keep_floor\tfloor_surplus_no_flash\tpool_base_delta\tpool_quote_delta\tpost_trade_dev_bps\tcw_burn_frac\t${efficiencyLabel}\tguard_status`,
|
|
)
|
|
}
|
|
|
|
candidates.forEach((candidate, index) => {
|
|
if (targetFlashBorrow != null) {
|
|
console.log(
|
|
[
|
|
index + 1,
|
|
fmt(candidate.grossBaseIn),
|
|
fmt(candidate.repayMargin),
|
|
fmt(candidate.floorMarginAfterTargetFlash),
|
|
fmt(candidate.poolBaseDelta),
|
|
fmt(candidate.poolQuoteDelta),
|
|
fmt(candidate.postTradeDeviationBps),
|
|
candidate.inventoryBurnFraction == null ? 'n/a' : fmt(candidate.inventoryBurnFraction),
|
|
fmt(candidateEfficiencyValue(candidate, targetFlashBorrow)),
|
|
formatGuardStatus(candidate),
|
|
].join('\t'),
|
|
)
|
|
return
|
|
}
|
|
console.log(
|
|
[
|
|
index + 1,
|
|
fmt(candidate.grossBaseIn),
|
|
fmt(candidate.maxFlashKeepFloor),
|
|
fmt(candidate.floorSurplusNoFlash),
|
|
fmt(candidate.poolBaseDelta),
|
|
fmt(candidate.poolQuoteDelta),
|
|
fmt(candidate.postTradeDeviationBps),
|
|
candidate.inventoryBurnFraction == null ? 'n/a' : fmt(candidate.inventoryBurnFraction),
|
|
fmt(candidateEfficiencyValue(candidate, targetFlashBorrow)),
|
|
formatGuardStatus(candidate),
|
|
].join('\t'),
|
|
)
|
|
})
|
|
|
|
if (recommended) {
|
|
console.log('')
|
|
if (targetFlashBorrow != null) {
|
|
console.log(
|
|
` Recommended tranche: gross_cw_in=${fmt(recommended.grossBaseIn)} with repay_margin=${fmt(recommended.repayMargin)}, floor_margin_after_repay=${fmt(recommended.floorMarginAfterTargetFlash)}, post_trade_dev_bps=${fmt(recommended.postTradeDeviationBps)}, ${efficiencyLabel}=${fmt(candidateEfficiencyValue(recommended, targetFlashBorrow))}.`,
|
|
)
|
|
} else {
|
|
console.log(
|
|
` Recommended tranche: gross_cw_in=${fmt(recommended.grossBaseIn)} with max_flash_keep_floor=${fmt(recommended.maxFlashKeepFloor)}, floor_surplus_no_flash=${fmt(recommended.floorSurplusNoFlash)}, post_trade_dev_bps=${fmt(recommended.postTradeDeviationBps)}, ${efficiencyLabel}=${fmt(candidateEfficiencyValue(recommended, targetFlashBorrow))}.`,
|
|
)
|
|
}
|
|
if (winningExplanation) console.log(` Why it won: ${winningExplanation}`)
|
|
if (guardConfig.cooldownBlocks > 0) {
|
|
console.log(` Execute once, then wait ${guardConfig.cooldownBlocks} blocks before the next tranche.`)
|
|
}
|
|
if (flashAddToPool && targetFlashBorrow != null && !recommended.samePoolQuoteDeepeningWithPmmRepayPossible) {
|
|
console.log(
|
|
' Reserve invariant: this recommendation can still deepen base B, but it cannot both repay the same PMM-funded flash top-up and leave quote reserve Q above the starting level without outside proceeds.',
|
|
)
|
|
}
|
|
} else if (candidates[0]) {
|
|
console.log('')
|
|
console.log(
|
|
` No tranche passes the active guards. Top-ranked ineligible candidate gross_cw_in=${fmt(candidates[0].grossBaseIn)} fails ${formatGuardStatus(candidates[0])}.`,
|
|
)
|
|
}
|
|
if (flashAddToPool && targetFlashBorrow != null) {
|
|
const anySamePoolQuoteDeepening = candidates.some(
|
|
(candidate) => candidate.samePoolQuoteDeepeningWithPmmRepayPossible,
|
|
)
|
|
if (!anySamePoolQuoteDeepening) {
|
|
console.log(
|
|
' Invariant: no tested tranche can both repay the same-pool flash top-up and end with higher quote reserve Q. Use outside proceeds if quote-side deepening is the goal.',
|
|
)
|
|
}
|
|
}
|
|
console.log('')
|
|
}
|
|
|
|
function runExamples() {
|
|
console.log('PMM flash push — baseline recalc (same units on all amounts)\n')
|
|
const B = 10e6
|
|
const Q = 10e6
|
|
const flash = 5
|
|
|
|
printScenario('$10M trade, 0 LP fee, external 1.00', B, Q, 10e6, 0, flash, 1.0, 0)
|
|
printScenario('$10M trade, 3 LP fee bps, external 1.00', B, Q, 10e6, 3, flash, 1.0, 0)
|
|
printScenario('$2M trade, 0 LP fee, external 1.00', B, Q, 2e6, 0, flash, 1.0, 0)
|
|
printScenario('$2M trade, 3 LP fee bps, external 1.00', B, Q, 2e6, 3, flash, 1.0, 0)
|
|
printScenario('$10M trade, 0 LP fee — break-even check P_ext=2.001', B, Q, 10e6, 0, flash, 2.001, 0)
|
|
printScenario('$2M trade, 0 LP fee — break-even check P_ext≈1.201', B, Q, 2e6, 0, flash, 1.2006, 0)
|
|
}
|
|
|
|
function main() {
|
|
const { values, tokens } = parseArgs({
|
|
options: {
|
|
examples: { type: 'boolean', short: 'e' },
|
|
'base-reserve': { type: 'string', short: 'B' },
|
|
'quote-reserve': { type: 'string', short: 'Q' },
|
|
trade: { type: 'string', short: 'x' },
|
|
'lp-fee-bps': { type: 'string', default: '3' },
|
|
'flash-fee-bps': { type: 'string', default: '5' },
|
|
'external-price': { type: 'string' },
|
|
'exit-fee-bps': { type: 'string', default: '0' },
|
|
scan: { type: 'string' },
|
|
'seed-pools': { type: 'string' },
|
|
'target-quote-per-pool': { type: 'string' },
|
|
'target-total-quote': { type: 'string' },
|
|
'inventory-base': { type: 'string' },
|
|
'loop-count': { type: 'string' },
|
|
'sequential-matched-loops': { type: 'string' },
|
|
'matched-flash-numerator': { type: 'string' },
|
|
'matched-flash-denominator': { type: 'string' },
|
|
'inventory-loop-scan': { type: 'boolean' },
|
|
'inventory-loop-rank': { type: 'boolean' },
|
|
'min-retained-usdc': { type: 'string', default: '0' },
|
|
'target-flash-borrow': { type: 'string' },
|
|
'flash-add-to-pool': { type: 'boolean' },
|
|
'full-loop-dry-run': { type: 'boolean' },
|
|
'public-live-sweep': { type: 'boolean' },
|
|
'public-flash-source': { type: 'string', default: 'aave' },
|
|
'loop-strategy': { type: 'string', default: 'quote-push' },
|
|
'owned-base-add': { type: 'string', default: '0' },
|
|
'flash-topup-quote': { type: 'string', default: '0' },
|
|
'atomic-bridge-base-amount': { type: 'string', default: '0' },
|
|
'atomic-destination-amount': { type: 'string' },
|
|
'atomic-destination-asset': { type: 'string' },
|
|
'atomic-destination-decimals': { type: 'string' },
|
|
'atomic-corridor-label': { type: 'string' },
|
|
'scan-atomic-bridge-max': { type: 'boolean' },
|
|
'flash-provider-cap': { type: 'string' },
|
|
'flash-provider-name': { type: 'string', default: 'flash provider' },
|
|
'flash-provider-address': { type: 'string' },
|
|
'flash-provider-token-address': { type: 'string' },
|
|
'flash-provider-rpc-url': { type: 'string' },
|
|
'external-via-asset': { type: 'string' },
|
|
'external-fee-bps': { type: 'string' },
|
|
'external-entry-price': { type: 'string' },
|
|
'external-exit-price': { type: 'string' },
|
|
'external-entry-fee-bps': { type: 'string' },
|
|
'external-exit-fee-bps': { type: 'string' },
|
|
'external-entry-price-cmd': { type: 'string' },
|
|
'external-exit-price-cmd': { type: 'string' },
|
|
'external-entry-source': { type: 'string', default: 'manual' },
|
|
'external-exit-source': { type: 'string', default: 'manual' },
|
|
'measured-gas-quote': { type: 'string' },
|
|
'measured-gas-source': { type: 'string', default: 'CLI measured gas quote override' },
|
|
'execution-grade': { type: 'boolean' },
|
|
'rank-profile': { type: 'string', default: 'balanced' },
|
|
'funding-readiness': { type: 'boolean' },
|
|
'inventory-asset': { type: 'string', default: 'cWUSDC' },
|
|
'base-asset': { type: 'string', default: 'base' },
|
|
'base-decimals': { type: 'string', default: '6' },
|
|
'source-asset': { type: 'string' },
|
|
'quote-asset': { type: 'string', default: 'USDC' },
|
|
'quote-decimals': { type: 'string', default: '6' },
|
|
'balance-report': { type: 'string' },
|
|
'gas-tx-count': { type: 'string' },
|
|
'gas-per-tx': { type: 'string' },
|
|
'max-fee-gwei': { type: 'string' },
|
|
'native-token-price': { type: 'string' },
|
|
'oracle-price': { type: 'string', default: '1' },
|
|
'max-post-trade-deviation-bps': { type: 'string' },
|
|
'max-cw-burn-fraction': { type: 'string' },
|
|
'cooldown-blocks': { type: 'string', default: '0' },
|
|
'mainnet-map': { type: 'boolean' },
|
|
'compare-deepen': { type: 'string' },
|
|
help: { type: 'boolean', short: 'h' },
|
|
},
|
|
allowPositionals: false,
|
|
tokens: true,
|
|
})
|
|
|
|
if (values.help) {
|
|
console.log(`Usage:
|
|
--examples, -e Print $10M / $2M baseline vs plan (10M/10M reserves)
|
|
--mainnet-map Print Ethereum mainnet cWUSDC/USDC pool map from deployment-status.json
|
|
--compare-deepen D With --mainnet-map: add D to base reserve and tabulate slippage (-B -Q required)
|
|
-B, --base-reserve Base token reserve (units)
|
|
-Q, --quote-reserve Quote token reserve (units)
|
|
-x, --trade Gross quote amount in (flash borrow / swap size)
|
|
--lp-fee-bps Pool fee on input (default 3 = 0.03%%; with --compare-deepen, overrides registry fee)
|
|
--flash-fee-bps Flash fee on repayment (default 5 = 0.05%%; use 0 for funded-inventory loop)
|
|
--external-price Generic gross quote-per-base external price; used as exit price in quote-push mode or entry price in base-unwind mode
|
|
--exit-fee-bps Legacy generic external fee/slippage bps (default 0); full-loop mode prefers the more specific external-* fee flags
|
|
--scan Comma-separated trade sizes (same units as -x)
|
|
--seed-pools Number of destination pools to seed with USDC quote
|
|
--target-quote-per-pool Target quote to add per pool in raw token units (e.g. 1000000 = 1 USDC if decimals=6)
|
|
--target-total-quote Total quote to raise in raw token units (alternative to per-pool target)
|
|
--inventory-base Available cW base inventory to compare against requirement
|
|
--loop-count Split the seeding plan into this many sequential loops (defaults to seed-pools or 1)
|
|
--sequential-matched-loops N Run N modeled quote-push loops; after each, set B=Q=endQuote (whitepaper ladder). Requires matched -B/-Q. Flash size: round(matched*num/den); defaults num/den=6342691/256763253
|
|
--matched-flash-numerator Override numerator for --sequential-matched-loops flash sizing (default 6342691)
|
|
--matched-flash-denominator Override denominator for --sequential-matched-loops flash sizing (default 256763253)
|
|
--inventory-loop-scan Treat --scan as gross cW sizes; compute safe flash ceilings and retained USDC
|
|
--inventory-loop-rank Rank tranche sizes by repay safety, retained floor, and retained-USDC-per-cW efficiency
|
|
--min-retained-usdc In inventory-loop-scan mode, keep at least this much USDC after flash repayment
|
|
--target-flash-borrow Optional borrow size to rank against actual repay/floor margins
|
|
--flash-add-to-pool Assume --target-flash-borrow is temporarily added to PMM quote reserve before the PMM leg
|
|
--full-loop-dry-run Simulate borrow -> strategy legs -> repay -> gas reserve -> pool seeding
|
|
--public-live-sweep Probe live mainnet public cWUSD*/USD* PMM pools and scan tranche sizes with recommendations
|
|
--public-flash-source Flash source for --public-live-sweep: aave|balancer|manual (default aave)
|
|
--loop-strategy quote-push (PMM quote-in then external unwind) or base-unwind (external acquire then PMM base-in)
|
|
--owned-base-add Permanently add this much owned base inventory to the source PMM before the modeled flash loop
|
|
--flash-topup-quote Temporarily add this much borrowed quote to source PMM before the modeled strategy leg
|
|
--atomic-bridge-base-amount Reserve this much PMM-acquired base into the atomic corridor before the external unwind
|
|
--atomic-destination-amount Immediate destination fulfillment amount to report for the atomic corridor (defaults to the reserved base amount, scaled by decimals)
|
|
--atomic-destination-asset Destination asset label for the atomic corridor report (e.g. cUSDC)
|
|
--atomic-destination-decimals Destination asset decimals for the atomic corridor report (default base decimals)
|
|
--atomic-corridor-label Human label for the atomic corridor, e.g. 1->138 cWUSDC->cUSDC
|
|
--scan-atomic-bridge-max Solve for the largest atomic corridor reservation that still passes repay-only and full positive-after-gas guards
|
|
--flash-provider-cap Optional flash source cap to enforce in full-loop dry-run or public-live-sweep
|
|
--flash-provider-name Label for the flash source in full-loop dry-run or manual public-live-sweep output
|
|
--flash-provider-address ERC-3156 flash lender address to probe live in full-loop dry-run (defaults to env FLASH_VAULT)
|
|
--flash-provider-token-address Token address to use with the ERC-3156 flash lender probe (defaults to env FLASH_VAULT_TOKEN)
|
|
--flash-provider-rpc-url RPC URL for the flash lender probe (defaults to FLASH_PROVIDER_RPC_URL, then RPC_URL_138_PUBLIC, RPC_URL_138, CHAIN138_RPC_URL, ETHEREUM_MAINNET_RPC)
|
|
--external-via-asset Optional intermediate asset label for reporting, e.g. ETH
|
|
--external-fee-bps Generic external-route fee/slippage bps fallback for full-loop mode
|
|
--external-entry-price Effective gross quote-per-base acquisition price for external quote->base leg
|
|
--external-exit-price Effective gross quote-per-base monetization price for external base->quote leg
|
|
--external-entry-fee-bps Entry-leg fee/slippage bps override for full-loop mode
|
|
--external-exit-fee-bps Exit-leg fee/slippage bps override for full-loop mode
|
|
--external-entry-price-cmd Shell command that prints a live entry price; overrides --external-entry-price and marks the source live
|
|
--external-exit-price-cmd Shell command that prints a live exit price; overrides --external-exit-price and marks the source live
|
|
--external-entry-source Source label for entry pricing: live|manual|scenario (default manual)
|
|
--external-exit-source Source label for exit pricing: live|manual|scenario (default manual)
|
|
--measured-gas-quote Measured end-to-end gas reserve in human quote units; overrides modeled gas reserve
|
|
--measured-gas-source Label for the measured gas input
|
|
--execution-grade Require live pool quote surface + the strategy's live external quote source(s) + measured or live-estimated gas + an explicit post-trade deviation cap before calling a tranche executable
|
|
--rank-profile Rank eligible tranches as conservative|balanced|efficiency (default balanced)
|
|
--funding-readiness Compare live mainnet funded inventory vs bridge-reachable Chain 138 inventory for a target raise
|
|
--inventory-asset Inventory asset on mainnet for readiness mode (default cWUSDC)
|
|
--base-asset Base asset label for --full-loop-dry-run output (default base)
|
|
--base-decimals Base asset decimals for --full-loop-dry-run output/math (default 6)
|
|
--source-asset Source asset on Chain 138 for readiness mode (default inferred from inventory asset)
|
|
--quote-asset Quote asset label for readiness mode and --full-loop-dry-run (default USDC)
|
|
--quote-decimals Quote asset decimals for --full-loop-dry-run output/math (default 6)
|
|
--balance-report Optional explicit deployer balance report JSON path for readiness fallback
|
|
--gas-tx-count Reserve native gas for this many follow-up transactions (inventory-loop or full-loop dry-run)
|
|
--gas-per-tx Gas limit per reserved transaction, in native gas units
|
|
--max-fee-gwei Gas price cap in gwei for native reserve sizing
|
|
--native-token-price Quote price of the native gas token (e.g. ETH price in USDC)
|
|
--oracle-price Peg/oracle target in quote per cW base (default 1)
|
|
--max-post-trade-deviation-bps Hard cap on absolute post-trade peg deviation
|
|
--max-cw-burn-fraction Hard cap on gross cW burn as a fraction of --inventory-base
|
|
--cooldown-blocks Execution note: wait this many blocks between eligible tranches
|
|
-h, --help This help
|
|
`)
|
|
process.exit(0)
|
|
}
|
|
|
|
if (values.examples) {
|
|
runExamples()
|
|
process.exit(0)
|
|
}
|
|
|
|
const B = values['base-reserve'] != null ? parseNum(values['base-reserve']) : null
|
|
const Q = values['quote-reserve'] != null ? parseNum(values['quote-reserve']) : null
|
|
const lpFeeBps = parseNum(values['lp-fee-bps'] ?? '3')
|
|
const lpFeeBpsWasExplicit = tokens.some((token) => token.kind === 'option' && token.name === 'lp-fee-bps')
|
|
const flashFeeBps = parseNum(values['flash-fee-bps'] ?? '5')
|
|
const exitFeeBps = parseNum(values['exit-fee-bps'] ?? '0')
|
|
const externalPrice =
|
|
values['external-price'] != null ? parseNum(values['external-price']) : undefined
|
|
const seedPools =
|
|
values['seed-pools'] != null ? parsePositiveInt(values['seed-pools'], '--seed-pools') : null
|
|
const targetQuotePerPool =
|
|
values['target-quote-per-pool'] != null ? parseNum(values['target-quote-per-pool']) : null
|
|
const targetTotalQuote =
|
|
values['target-total-quote'] != null ? parseNum(values['target-total-quote']) : null
|
|
const inventoryBase =
|
|
values['inventory-base'] != null ? parseNum(values['inventory-base']) : null
|
|
const loopCountOverride =
|
|
values['loop-count'] != null ? parsePositiveInt(values['loop-count'], '--loop-count') : null
|
|
const sequentialMatchedLoops =
|
|
values['sequential-matched-loops'] != null
|
|
? parsePositiveInt(values['sequential-matched-loops'], '--sequential-matched-loops')
|
|
: null
|
|
const matchedFlashNumerator =
|
|
values['matched-flash-numerator'] != null
|
|
? parsePositiveInt(values['matched-flash-numerator'], '--matched-flash-numerator')
|
|
: 6342691
|
|
const matchedFlashDenominator =
|
|
values['matched-flash-denominator'] != null
|
|
? parsePositiveInt(values['matched-flash-denominator'], '--matched-flash-denominator')
|
|
: 256763253
|
|
const minRetainedUsdc = parseNum(values['min-retained-usdc'] ?? '0')
|
|
const targetFlashBorrow =
|
|
values['target-flash-borrow'] != null ? parseNum(values['target-flash-borrow']) : null
|
|
const flashAddToPool = values['flash-add-to-pool']
|
|
const fullLoopDryRunMode = values['full-loop-dry-run']
|
|
const loopStrategy = String(values['loop-strategy'] ?? 'quote-push')
|
|
const ownedBaseAdd = parseNum(values['owned-base-add'] ?? '0')
|
|
const flashTopupQuote = parseNum(values['flash-topup-quote'] ?? '0')
|
|
const atomicBridgeBaseAmount = parseNum(values['atomic-bridge-base-amount'] ?? '0')
|
|
const atomicDestinationAmount =
|
|
values['atomic-destination-amount'] != null ? parseNum(values['atomic-destination-amount']) : null
|
|
const atomicDestinationAsset =
|
|
values['atomic-destination-asset'] != null ? String(values['atomic-destination-asset']) : null
|
|
const atomicDestinationDecimals =
|
|
values['atomic-destination-decimals'] != null
|
|
? parseNonNegativeInt(values['atomic-destination-decimals'], '--atomic-destination-decimals')
|
|
: null
|
|
const atomicCorridorLabel =
|
|
values['atomic-corridor-label'] != null ? String(values['atomic-corridor-label']) : null
|
|
const scanAtomicBridgeMax = values['scan-atomic-bridge-max']
|
|
const flashProviderCap =
|
|
values['flash-provider-cap'] != null ? parseNum(values['flash-provider-cap']) : null
|
|
const flashProviderName = String(values['flash-provider-name'] ?? 'flash provider')
|
|
const flashProviderAddress =
|
|
values['flash-provider-address'] != null
|
|
? String(values['flash-provider-address'])
|
|
: (process.env.FLASH_VAULT ?? null)
|
|
const flashProviderTokenAddress =
|
|
values['flash-provider-token-address'] != null
|
|
? String(values['flash-provider-token-address'])
|
|
: (process.env.FLASH_VAULT_TOKEN ?? null)
|
|
const flashProviderRpcUrl =
|
|
values['flash-provider-rpc-url'] != null
|
|
? String(values['flash-provider-rpc-url'])
|
|
: process.env.FLASH_PROVIDER_RPC_URL ??
|
|
process.env.RPC_URL_138_PUBLIC ??
|
|
process.env.RPC_URL_138 ??
|
|
process.env.CHAIN138_RPC_URL ??
|
|
process.env.ETHEREUM_MAINNET_RPC ??
|
|
null
|
|
const externalViaAsset = values['external-via-asset'] != null ? String(values['external-via-asset']) : null
|
|
const externalFeeBps =
|
|
values['external-fee-bps'] != null ? parseNum(values['external-fee-bps']) : exitFeeBps
|
|
const externalEntryPriceCmd =
|
|
values['external-entry-price-cmd'] != null ? String(values['external-entry-price-cmd']) : null
|
|
const externalExitPriceCmd =
|
|
values['external-exit-price-cmd'] != null ? String(values['external-exit-price-cmd']) : null
|
|
const liveExternalEntryPrice = tryReadNumericCommand(
|
|
externalEntryPriceCmd,
|
|
externalEntryPriceCmd ? `live command: ${externalEntryPriceCmd}` : null,
|
|
)
|
|
const liveExternalExitPrice = tryReadNumericCommand(
|
|
externalExitPriceCmd,
|
|
externalExitPriceCmd ? `live command: ${externalExitPriceCmd}` : null,
|
|
)
|
|
const externalEntryPrice =
|
|
liveExternalEntryPrice?.value ??
|
|
(values['external-entry-price'] != null ? parseNum(values['external-entry-price']) : externalPrice)
|
|
const externalExitPrice =
|
|
liveExternalExitPrice?.value ??
|
|
(values['external-exit-price'] != null ? parseNum(values['external-exit-price']) : externalPrice)
|
|
const externalEntryFeeBps =
|
|
values['external-entry-fee-bps'] != null ? parseNum(values['external-entry-fee-bps']) : externalFeeBps
|
|
const externalExitFeeBps =
|
|
values['external-exit-fee-bps'] != null ? parseNum(values['external-exit-fee-bps']) : externalFeeBps
|
|
const externalEntrySource = liveExternalEntryPrice ? 'live' : String(values['external-entry-source'] ?? 'manual')
|
|
const externalExitSource = liveExternalExitPrice ? 'live' : String(values['external-exit-source'] ?? 'manual')
|
|
const measuredGasQuote =
|
|
values['measured-gas-quote'] != null ? parseNum(values['measured-gas-quote']) : null
|
|
const measuredGasSource = String(values['measured-gas-source'] ?? 'CLI measured gas quote override')
|
|
const executionGradeRequired = values['execution-grade']
|
|
const rankProfile = String(values['rank-profile'] ?? 'balanced')
|
|
const fundingReadinessMode = values['funding-readiness']
|
|
const inventoryAsset = String(values['inventory-asset'] ?? 'cWUSDC')
|
|
const baseAsset = String(values['base-asset'] ?? 'base')
|
|
const baseDecimals = parseNonNegativeInt(values['base-decimals'] ?? '6', '--base-decimals')
|
|
const sourceAsset = values['source-asset'] != null ? String(values['source-asset']) : inferSourceAsset(inventoryAsset)
|
|
const quoteAsset = String(values['quote-asset'] ?? 'USDC')
|
|
const quoteDecimals = parseNonNegativeInt(values['quote-decimals'] ?? '6', '--quote-decimals')
|
|
const balanceReportPath =
|
|
values['balance-report'] != null ? resolve(process.cwd(), String(values['balance-report'])) : latestBalanceReportPath()
|
|
const gasTxCount =
|
|
values['gas-tx-count'] != null ? parsePositiveInt(values['gas-tx-count'], '--gas-tx-count') : null
|
|
const gasPerTx = values['gas-per-tx'] != null ? parseNum(values['gas-per-tx']) : null
|
|
const maxFeeGwei = values['max-fee-gwei'] != null ? parseNum(values['max-fee-gwei']) : null
|
|
const nativeTokenPrice =
|
|
values['native-token-price'] != null ? parseNum(values['native-token-price']) : null
|
|
const oraclePrice = parseNum(values['oracle-price'] ?? '1')
|
|
const maxPostTradeDeviationBps =
|
|
values['max-post-trade-deviation-bps'] != null
|
|
? parseNum(values['max-post-trade-deviation-bps'])
|
|
: null
|
|
const maxCwBurnFraction =
|
|
values['max-cw-burn-fraction'] != null ? parseNum(values['max-cw-burn-fraction']) : null
|
|
const cooldownBlocks = parseNonNegativeInt(values['cooldown-blocks'] ?? '0', '--cooldown-blocks')
|
|
const inventoryLoopScanMode = values['inventory-loop-scan']
|
|
const inventoryLoopRankMode = values['inventory-loop-rank']
|
|
const inventoryLoopMode = inventoryLoopScanMode || inventoryLoopRankMode
|
|
const publicLiveSweepMode = values['public-live-sweep']
|
|
const publicFlashSource = String(values['public-flash-source'] ?? 'aave')
|
|
const gasReserveFlagsUsed = gasTxCount != null || gasPerTx != null || maxFeeGwei != null
|
|
const extraGuardFlagsUsed =
|
|
gasReserveFlagsUsed ||
|
|
measuredGasQuote != null ||
|
|
maxPostTradeDeviationBps != null ||
|
|
maxCwBurnFraction != null ||
|
|
cooldownBlocks > 0
|
|
|
|
const seedMode =
|
|
!fundingReadinessMode &&
|
|
!inventoryLoopMode &&
|
|
!publicLiveSweepMode &&
|
|
(
|
|
seedPools != null ||
|
|
targetQuotePerPool != null ||
|
|
targetTotalQuote != null ||
|
|
inventoryBase != null ||
|
|
loopCountOverride != null
|
|
)
|
|
|
|
if (
|
|
!fundingReadinessMode &&
|
|
tokens.some(
|
|
(token) =>
|
|
token.kind === 'option' &&
|
|
['inventory-asset', 'source-asset', 'balance-report'].includes(token.name),
|
|
)
|
|
) {
|
|
console.error(
|
|
'Error: --inventory-asset, --source-asset, and --balance-report are only supported with --funding-readiness',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!['conservative', 'balanced', 'efficiency'].includes(rankProfile)) {
|
|
console.error('Error: --rank-profile must be one of conservative, balanced, efficiency')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!['live', 'manual', 'scenario'].includes(externalEntrySource)) {
|
|
console.error('Error: --external-entry-source must be one of live, manual, scenario')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!['live', 'manual', 'scenario'].includes(externalExitSource)) {
|
|
console.error('Error: --external-exit-source must be one of live, manual, scenario')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!['aave', 'balancer', 'manual'].includes(publicFlashSource)) {
|
|
console.error('Error: --public-flash-source must be one of aave, balancer, manual')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (tokens.some((token) => token.kind === 'option' && token.name === 'public-flash-source') && !publicLiveSweepMode) {
|
|
console.error('Error: --public-flash-source is only supported with --public-live-sweep')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (tokens.some((token) => token.kind === 'option' && token.name === 'rank-profile') && !inventoryLoopRankMode) {
|
|
console.error('Error: --rank-profile is only supported with --inventory-loop-rank')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!['quote-push', 'base-unwind', 'both'].includes(loopStrategy)) {
|
|
console.error('Error: --loop-strategy must be one of quote-push, base-unwind, both')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (loopStrategy === 'both' && !publicLiveSweepMode) {
|
|
console.error('Error: --loop-strategy both is only supported with --public-live-sweep')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (flashAddToPool && !inventoryLoopMode) {
|
|
console.error('Error: --flash-add-to-pool is only supported with --inventory-loop-scan or --inventory-loop-rank')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (
|
|
tokens.some((token) => token.kind === 'option' && token.name === 'flash-provider-cap') &&
|
|
!fullLoopDryRunMode &&
|
|
!publicLiveSweepMode &&
|
|
sequentialMatchedLoops == null
|
|
) {
|
|
console.error(
|
|
'Error: --flash-provider-cap is only supported with --full-loop-dry-run, --public-live-sweep, or --sequential-matched-loops',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
if (
|
|
tokens.some((token) => token.kind === 'option' && token.name === 'flash-provider-name') &&
|
|
!fullLoopDryRunMode &&
|
|
!publicLiveSweepMode &&
|
|
sequentialMatchedLoops == null
|
|
) {
|
|
console.error(
|
|
'Error: --flash-provider-name is only supported with --full-loop-dry-run, --public-live-sweep, or --sequential-matched-loops',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
if (
|
|
tokens.some((token) =>
|
|
token.kind === 'option' &&
|
|
['flash-provider-address', 'flash-provider-token-address', 'flash-provider-rpc-url'].includes(token.name),
|
|
) &&
|
|
!fullLoopDryRunMode
|
|
) {
|
|
console.error(
|
|
'Error: --flash-provider-address, --flash-provider-token-address, and --flash-provider-rpc-url are only supported with --full-loop-dry-run',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
if (
|
|
tokens.some((token) =>
|
|
token.kind === 'option' &&
|
|
[
|
|
'loop-strategy',
|
|
'owned-base-add',
|
|
'flash-topup-quote',
|
|
'atomic-bridge-base-amount',
|
|
'atomic-destination-amount',
|
|
'atomic-destination-asset',
|
|
'atomic-destination-decimals',
|
|
'atomic-corridor-label',
|
|
'scan-atomic-bridge-max',
|
|
'external-via-asset',
|
|
'external-fee-bps',
|
|
'external-entry-price',
|
|
'external-exit-price',
|
|
'external-entry-fee-bps',
|
|
'external-exit-fee-bps',
|
|
'external-entry-source',
|
|
'external-exit-source',
|
|
'measured-gas-quote',
|
|
'measured-gas-source',
|
|
'execution-grade',
|
|
].includes(token.name),
|
|
) &&
|
|
!fullLoopDryRunMode &&
|
|
!publicLiveSweepMode &&
|
|
sequentialMatchedLoops == null
|
|
) {
|
|
console.error(
|
|
'Error: strategy routing flags are only supported with --full-loop-dry-run, --public-live-sweep, or --sequential-matched-loops',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
if (flashAddToPool && targetFlashBorrow == null) {
|
|
console.error('Error: --flash-add-to-pool requires --target-flash-borrow')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (
|
|
extraGuardFlagsUsed &&
|
|
!inventoryLoopMode &&
|
|
!fullLoopDryRunMode &&
|
|
!publicLiveSweepMode &&
|
|
sequentialMatchedLoops == null
|
|
) {
|
|
console.error(
|
|
'Error: gas reserve and peg-safety guard flags are only supported with --inventory-loop-scan, --inventory-loop-rank, --full-loop-dry-run, --public-live-sweep, or --sequential-matched-loops',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
if ((fullLoopDryRunMode || publicLiveSweepMode || sequentialMatchedLoops != null) && maxCwBurnFraction != null) {
|
|
console.error('Error: --max-cw-burn-fraction is only supported with --inventory-loop-scan or --inventory-loop-rank')
|
|
process.exit(1)
|
|
}
|
|
|
|
if ((fullLoopDryRunMode || publicLiveSweepMode || sequentialMatchedLoops != null) && cooldownBlocks > 0) {
|
|
console.error('Error: --cooldown-blocks is only supported with --inventory-loop-scan or --inventory-loop-rank')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!(flashTopupQuote >= 0)) {
|
|
console.error('Error: --flash-topup-quote must be non-negative')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!(atomicBridgeBaseAmount >= 0)) {
|
|
console.error('Error: --atomic-bridge-base-amount must be non-negative')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (atomicDestinationAmount != null && !(atomicDestinationAmount >= 0)) {
|
|
console.error('Error: --atomic-destination-amount must be non-negative')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!(ownedBaseAdd >= 0)) {
|
|
console.error('Error: --owned-base-add must be non-negative')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!(oraclePrice > 0)) {
|
|
console.error('Error: --oracle-price must be greater than zero')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (gasReserveFlagsUsed && measuredGasQuote != null) {
|
|
console.error('Error: use either modeled gas inputs or --measured-gas-quote, not both')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (gasReserveFlagsUsed) {
|
|
if (gasTxCount == null || gasPerTx == null || maxFeeGwei == null || nativeTokenPrice == null) {
|
|
console.error(
|
|
'Error: native gas reserve requires --gas-tx-count, --gas-per-tx, --max-fee-gwei, and --native-token-price together',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
if (!(gasPerTx > 0) || !(maxFeeGwei > 0) || !(nativeTokenPrice > 0)) {
|
|
console.error('Error: --gas-per-tx, --max-fee-gwei, and --native-token-price must be greater than zero')
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
if (measuredGasQuote != null && !(measuredGasQuote > 0)) {
|
|
console.error('Error: --measured-gas-quote must be greater than zero')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (maxPostTradeDeviationBps != null && !(maxPostTradeDeviationBps >= 0)) {
|
|
console.error('Error: --max-post-trade-deviation-bps must be non-negative')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!(externalFeeBps >= 0) || !(externalEntryFeeBps >= 0) || !(externalExitFeeBps >= 0)) {
|
|
console.error('Error: external fee/slippage bps values must be non-negative')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (maxCwBurnFraction != null) {
|
|
if (inventoryBase == null) {
|
|
console.error('Error: --max-cw-burn-fraction requires --inventory-base')
|
|
process.exit(1)
|
|
}
|
|
if (!(maxCwBurnFraction >= 0 && maxCwBurnFraction <= 1)) {
|
|
console.error('Error: --max-cw-burn-fraction must be between 0 and 1')
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
const deployerAddressForLiveProbe =
|
|
process.env.DEPLOYER_ADDRESS ?? '0x4A666F96fC8764181194447A7dFdb7d471b301C8'
|
|
|
|
const inventoryLoopGuardConfig = buildInventoryLoopGuardConfig({
|
|
minRetainedUsdc,
|
|
gasTxCount,
|
|
gasPerTx,
|
|
maxFeeGwei,
|
|
nativeTokenPrice,
|
|
quoteDecimals,
|
|
oraclePrice,
|
|
maxPostTradeDeviationBps,
|
|
maxCwBurnFraction,
|
|
cooldownBlocks,
|
|
measuredGasQuote,
|
|
measuredGasSource,
|
|
estimatedGasQuote: null,
|
|
estimatedGasSource: null,
|
|
})
|
|
|
|
if (sequentialMatchedLoops != null) {
|
|
if (
|
|
fullLoopDryRunMode ||
|
|
publicLiveSweepMode ||
|
|
fundingReadinessMode ||
|
|
inventoryLoopScanMode ||
|
|
inventoryLoopRankMode ||
|
|
seedMode
|
|
) {
|
|
console.error('Error: --sequential-matched-loops cannot be combined with other primary modes')
|
|
process.exit(1)
|
|
}
|
|
if (scanAtomicBridgeMax) {
|
|
console.error('Error: --scan-atomic-bridge-max is only supported with --full-loop-dry-run')
|
|
process.exit(1)
|
|
}
|
|
if (B == null || Q == null) {
|
|
console.error('Error: --sequential-matched-loops requires -B and -Q')
|
|
process.exit(1)
|
|
}
|
|
if (loopStrategy !== 'quote-push') {
|
|
console.error('Error: --sequential-matched-loops requires --loop-strategy quote-push')
|
|
process.exit(1)
|
|
}
|
|
if (externalExitPrice == null) {
|
|
console.error(
|
|
'Error: --sequential-matched-loops (quote-push) requires --external-exit-price or --external-price',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
const firstLoopFlash = Math.max(
|
|
1,
|
|
Math.round((Math.min(B, Q) * matchedFlashNumerator) / matchedFlashDenominator),
|
|
)
|
|
if (flashTopupQuote > firstLoopFlash) {
|
|
console.error('Error: --flash-topup-quote cannot exceed the first-loop modeled flash borrow size')
|
|
process.exit(1)
|
|
}
|
|
if (flashProviderAddress && flashProviderTokenAddress) {
|
|
console.error(
|
|
'Error: --sequential-matched-loops does not support ERC-3156 flash probes; omit --flash-provider-address/--flash-provider-token-address',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
const mainnetPoolContextSeq = loadMainnetPool(baseAsset, quoteAsset)
|
|
const livePoolFeeBpsSeq =
|
|
mainnetPoolContextSeq?.poolAddress != null
|
|
? tryReadPoolFeeBpsViaCast(1, mainnetPoolContextSeq.poolAddress)
|
|
: null
|
|
const lpFeeSequential = !lpFeeBpsWasExplicit && livePoolFeeBpsSeq != null ? livePoolFeeBpsSeq : lpFeeBps
|
|
|
|
printSequentialMatchedLadder({
|
|
iterations: sequentialMatchedLoops,
|
|
startB: B,
|
|
startQ: Q,
|
|
flashNumerator: matchedFlashNumerator,
|
|
flashDenominator: matchedFlashDenominator,
|
|
lpFeeBps: lpFeeSequential,
|
|
flashFeeBps,
|
|
externalExitPrice,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
quoteAsset,
|
|
baseAsset,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
guardConfig: inventoryLoopGuardConfig,
|
|
flashProviderCap,
|
|
flashProviderName,
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset: atomicDestinationAsset ?? baseAsset,
|
|
atomicDestinationDecimals: atomicDestinationDecimals ?? baseDecimals,
|
|
atomicCorridorLabel,
|
|
})
|
|
process.exit(0)
|
|
}
|
|
|
|
if (fundingReadinessMode) {
|
|
if (targetQuotePerPool != null && targetTotalQuote != null) {
|
|
console.error('Error: use either --target-quote-per-pool or --target-total-quote with --funding-readiness, not both')
|
|
process.exit(1)
|
|
}
|
|
if (targetQuotePerPool == null && targetTotalQuote == null) {
|
|
console.error('Error: --funding-readiness requires --target-quote-per-pool or --target-total-quote')
|
|
process.exit(1)
|
|
}
|
|
if (targetQuotePerPool != null && seedPools == null && loopCountOverride == null) {
|
|
console.error('Error: --target-quote-per-pool requires --seed-pools or --loop-count with --funding-readiness')
|
|
process.exit(1)
|
|
}
|
|
|
|
const totalQuoteTarget =
|
|
targetTotalQuote != null ? targetTotalQuote : (seedPools ?? loopCountOverride) * targetQuotePerPool
|
|
if (!(totalQuoteTarget > 0)) {
|
|
console.error('Error: readiness target quote must be positive')
|
|
process.exit(1)
|
|
}
|
|
|
|
const mainnetPool = loadMainnetPool(inventoryAsset, quoteAsset)
|
|
if (!mainnetPool) {
|
|
console.error(
|
|
`Error: could not find a mainnet pool row for ${inventoryAsset}/${quoteAsset} in cross-chain-pmm-lps/config/deployment-status.json`,
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
const resolvedReserves =
|
|
B != null && Q != null
|
|
? { baseReserve: B, quoteReserve: Q, source: 'CLI -B/-Q' }
|
|
: tryReadPoolReservesViaCast(1, mainnetPool.poolAddress)
|
|
if (!resolvedReserves) {
|
|
console.error(
|
|
'Error: --funding-readiness requires -B/-Q or a reachable Ethereum mainnet RPC to read live pool reserves',
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
const readinessLpFeeBps = !lpFeeBpsWasExplicit && mainnetPool.feeBps != null ? mainnetPool.feeBps : lpFeeBps
|
|
const report = loadBalanceReport(balanceReportPath)
|
|
printFundingReadiness({
|
|
inventoryAsset,
|
|
sourceAsset,
|
|
quoteAsset,
|
|
totalQuoteTarget,
|
|
lpFeeBps: readinessLpFeeBps,
|
|
baseReserve: resolvedReserves.baseReserve,
|
|
quoteReserve: resolvedReserves.quoteReserve,
|
|
reserveSource: resolvedReserves.source,
|
|
poolAddress: mainnetPool.poolAddress,
|
|
report,
|
|
reportPath: balanceReportPath,
|
|
})
|
|
process.exit(0)
|
|
}
|
|
|
|
if (fullLoopDryRunMode) {
|
|
const x = values.trade != null ? parseNum(values.trade) : null
|
|
if (B == null || Q == null || x == null) {
|
|
console.error('Error: --full-loop-dry-run requires -B, -Q, and -x')
|
|
process.exit(1)
|
|
}
|
|
if (!(x > 0)) {
|
|
console.error('Error: -x must be greater than zero for --full-loop-dry-run')
|
|
process.exit(1)
|
|
}
|
|
if (flashTopupQuote > x) {
|
|
console.error('Error: --flash-topup-quote cannot exceed the flash borrow size -x')
|
|
process.exit(1)
|
|
}
|
|
if (atomicBridgeBaseAmount > 0 && loopStrategy !== 'quote-push') {
|
|
console.error('Error: --atomic-bridge-base-amount is only supported with --loop-strategy quote-push')
|
|
process.exit(1)
|
|
}
|
|
if (scanAtomicBridgeMax && loopStrategy !== 'quote-push') {
|
|
console.error('Error: --scan-atomic-bridge-max is only supported with --loop-strategy quote-push')
|
|
process.exit(1)
|
|
}
|
|
if (loopStrategy === 'quote-push' && externalExitPrice == null) {
|
|
console.error('Error: quote-push full-loop dry-run requires --external-exit-price or --external-price')
|
|
process.exit(1)
|
|
}
|
|
if (loopStrategy === 'base-unwind' && externalEntryPrice == null) {
|
|
console.error('Error: base-unwind full-loop dry-run requires --external-entry-price or --external-price')
|
|
process.exit(1)
|
|
}
|
|
if (flashProviderCap != null && !(flashProviderCap > 0)) {
|
|
console.error('Error: --flash-provider-cap must be greater than zero')
|
|
process.exit(1)
|
|
}
|
|
if ((flashProviderAddress == null) !== (flashProviderTokenAddress == null)) {
|
|
console.error('Error: --flash-provider-address and --flash-provider-token-address must be provided together')
|
|
process.exit(1)
|
|
}
|
|
if (targetQuotePerPool != null && targetTotalQuote != null) {
|
|
console.error('Error: use either --target-quote-per-pool or --target-total-quote with --full-loop-dry-run, not both')
|
|
process.exit(1)
|
|
}
|
|
if (targetQuotePerPool != null && seedPools == null && loopCountOverride == null) {
|
|
console.error('Error: --target-quote-per-pool requires --seed-pools or --loop-count with --full-loop-dry-run')
|
|
process.exit(1)
|
|
}
|
|
|
|
const probedFlashProvider =
|
|
flashProviderAddress && flashProviderTokenAddress
|
|
? tryProbeErc3156FlashProvider(flashProviderAddress, flashProviderTokenAddress, x, flashProviderRpcUrl)
|
|
: null
|
|
const effectiveFlashProviderCap =
|
|
probedFlashProvider?.maxFlashLoan != null && flashProviderCap != null
|
|
? Math.min(flashProviderCap, probedFlashProvider.maxFlashLoan)
|
|
: (probedFlashProvider?.maxFlashLoan ?? flashProviderCap)
|
|
const effectiveFlashFeeAmount = probedFlashProvider?.flashFeeAmount ?? null
|
|
const effectiveFlashFeeSource =
|
|
probedFlashProvider?.source ??
|
|
(flashProviderAddress && flashProviderTokenAddress
|
|
? `manual flash fee bps (${flashFeeBps}) — probe failed via ${flashProviderAddress}`
|
|
: null)
|
|
const mainnetPoolContext = loadMainnetPool(baseAsset, quoteAsset)
|
|
const livePoolFeeBps =
|
|
mainnetPoolContext?.poolAddress != null ? tryReadPoolFeeBpsViaCast(1, mainnetPoolContext.poolAddress) : null
|
|
const quoteSurface =
|
|
mainnetPoolContext?.poolAddress != null
|
|
? tryProbePoolQuoteSurfaceViaCast(
|
|
1,
|
|
mainnetPoolContext.poolAddress,
|
|
deployerAddressForLiveProbe,
|
|
)
|
|
: null
|
|
const estimatedMainnetGasQuote =
|
|
measuredGasQuote == null &&
|
|
mainnetPoolContext?.poolAddress != null &&
|
|
mainnetPoolContext.baseAddress != null &&
|
|
mainnetPoolContext.quoteAddress != null
|
|
? deriveEstimatedGasQuoteForMainnetStrategy({
|
|
strategy: loopStrategy,
|
|
poolAddress: mainnetPoolContext.poolAddress,
|
|
baseAddress: mainnetPoolContext.baseAddress,
|
|
quoteAddress: mainnetPoolContext.quoteAddress,
|
|
amountInQuoteRaw: x,
|
|
externalEntryPrice,
|
|
externalEntryFeeBps,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
nativeTokenPrice,
|
|
fromAddress: deployerAddressForLiveProbe,
|
|
})
|
|
: null
|
|
const fullLoopGuardConfig =
|
|
measuredGasQuote == null && estimatedMainnetGasQuote
|
|
? buildInventoryLoopGuardConfig({
|
|
minRetainedUsdc,
|
|
gasTxCount: null,
|
|
gasPerTx: null,
|
|
maxFeeGwei: null,
|
|
nativeTokenPrice,
|
|
quoteDecimals,
|
|
oraclePrice,
|
|
maxPostTradeDeviationBps,
|
|
maxCwBurnFraction,
|
|
cooldownBlocks,
|
|
measuredGasQuote: null,
|
|
measuredGasSource: null,
|
|
estimatedGasQuote: estimatedMainnetGasQuote.quoteCost,
|
|
estimatedGasSource: estimatedMainnetGasQuote.source,
|
|
})
|
|
: inventoryLoopGuardConfig
|
|
|
|
printFullLoopDryRun({
|
|
B,
|
|
Q,
|
|
x,
|
|
lpFeeBps: livePoolFeeBps ?? lpFeeBps,
|
|
flashFeeBps,
|
|
loopStrategy,
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
externalEntryPrice,
|
|
externalExitPrice,
|
|
externalEntryFeeBps,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
quoteAsset,
|
|
baseAsset,
|
|
quoteDecimals,
|
|
baseDecimals,
|
|
guardConfig: fullLoopGuardConfig,
|
|
flashProviderCap: effectiveFlashProviderCap,
|
|
flashProviderName,
|
|
flashFeeAmount: effectiveFlashFeeAmount,
|
|
flashFeeSource: effectiveFlashFeeSource,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount: loopCountOverride ?? seedPools ?? 1,
|
|
quoteSurface,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
livePoolFeeBps,
|
|
executionGradeRequired,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset: atomicDestinationAsset ?? baseAsset,
|
|
atomicDestinationDecimals: atomicDestinationDecimals ?? baseDecimals,
|
|
atomicCorridorLabel,
|
|
scanAtomicBridgeMax,
|
|
})
|
|
process.exit(0)
|
|
}
|
|
|
|
if (publicLiveSweepMode) {
|
|
if (targetQuotePerPool != null && targetTotalQuote != null) {
|
|
console.error('Error: use either --target-quote-per-pool or --target-total-quote with --public-live-sweep, not both')
|
|
process.exit(1)
|
|
}
|
|
if (targetQuotePerPool != null && seedPools == null && loopCountOverride == null) {
|
|
console.error('Error: --target-quote-per-pool requires --seed-pools or --loop-count with --public-live-sweep')
|
|
process.exit(1)
|
|
}
|
|
if ((loopStrategy === 'quote-push' || loopStrategy === 'both') && externalExitPrice == null) {
|
|
console.error('Error: quote-push public live sweep requires --external-exit-price or --external-price')
|
|
process.exit(1)
|
|
}
|
|
if ((loopStrategy === 'base-unwind' || loopStrategy === 'both') && externalEntryPrice == null) {
|
|
console.error('Error: base-unwind public live sweep requires --external-entry-price or --external-price')
|
|
process.exit(1)
|
|
}
|
|
if (publicFlashSource === 'manual' && flashProviderCap == null) {
|
|
console.error('Error: --public-flash-source manual requires --flash-provider-cap')
|
|
process.exit(1)
|
|
}
|
|
|
|
printPublicLiveSweep({
|
|
scanStr: values.scan ?? null,
|
|
loopStrategy,
|
|
ownedBaseAdd,
|
|
flashTopupQuote,
|
|
flashProviderCap,
|
|
flashProviderName,
|
|
flashFeeBps,
|
|
externalEntryPrice,
|
|
externalExitPrice,
|
|
externalEntryFeeBps,
|
|
externalExitFeeBps,
|
|
externalViaAsset,
|
|
atomicBridgeBaseAmount,
|
|
atomicDestinationAmount,
|
|
atomicDestinationAsset: atomicDestinationAsset ?? baseAsset,
|
|
atomicDestinationDecimals: atomicDestinationDecimals ?? baseDecimals,
|
|
atomicCorridorLabel,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
targetTotalQuote,
|
|
loopCount: loopCountOverride ?? seedPools ?? 1,
|
|
guardConfig: inventoryLoopGuardConfig,
|
|
publicFlashSource,
|
|
externalEntrySource,
|
|
externalExitSource,
|
|
executionGradeRequired,
|
|
})
|
|
process.exit(0)
|
|
}
|
|
|
|
if (seedMode) {
|
|
if (targetQuotePerPool != null && targetTotalQuote != null) {
|
|
console.error('Error: use either --target-quote-per-pool or --target-total-quote, not both')
|
|
process.exit(1)
|
|
}
|
|
if (targetQuotePerPool == null && targetTotalQuote == null) {
|
|
console.error('Error: seeding mode requires --target-quote-per-pool or --target-total-quote')
|
|
process.exit(1)
|
|
}
|
|
if (targetQuotePerPool != null && seedPools == null && loopCountOverride == null) {
|
|
console.error('Error: --target-quote-per-pool requires --seed-pools or --loop-count')
|
|
process.exit(1)
|
|
}
|
|
|
|
const totalQuoteTarget =
|
|
targetTotalQuote != null
|
|
? targetTotalQuote
|
|
: seedPools * targetQuotePerPool
|
|
const loopCount = loopCountOverride ?? seedPools ?? 1
|
|
|
|
if (!(totalQuoteTarget > 0)) {
|
|
console.error('Error: total quote target must be positive')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (B != null || Q != null) {
|
|
if (B == null || Q == null) {
|
|
console.error('Error: PMM seeding mode requires both -B and -Q')
|
|
process.exit(1)
|
|
}
|
|
printSeedingPlanFromPmm(
|
|
B,
|
|
Q,
|
|
totalQuoteTarget,
|
|
lpFeeBps,
|
|
inventoryBase,
|
|
loopCount,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
)
|
|
process.exit(0)
|
|
}
|
|
|
|
if (externalPrice == null) {
|
|
console.error('Error: seeding mode requires either -B/-Q for PMM source math or --external-price for external monetization')
|
|
process.exit(1)
|
|
}
|
|
|
|
printSeedingPlanExternal(
|
|
totalQuoteTarget,
|
|
externalPrice,
|
|
exitFeeBps,
|
|
inventoryBase,
|
|
loopCount,
|
|
seedPools,
|
|
targetQuotePerPool,
|
|
)
|
|
process.exit(0)
|
|
}
|
|
|
|
if (inventoryLoopScanMode) {
|
|
if (B == null || Q == null) {
|
|
console.error('Error: --inventory-loop-scan requires -B and -Q')
|
|
process.exit(1)
|
|
}
|
|
printInventoryLoopSafetyScan(
|
|
B,
|
|
Q,
|
|
values.scan ?? null,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
inventoryBase,
|
|
targetFlashBorrow,
|
|
inventoryLoopGuardConfig,
|
|
flashAddToPool,
|
|
)
|
|
process.exit(0)
|
|
}
|
|
|
|
if (inventoryLoopRankMode) {
|
|
if (B == null || Q == null) {
|
|
console.error('Error: --inventory-loop-rank requires -B and -Q')
|
|
process.exit(1)
|
|
}
|
|
printInventoryLoopRanking(
|
|
B,
|
|
Q,
|
|
values.scan ?? null,
|
|
lpFeeBps,
|
|
flashFeeBps,
|
|
inventoryBase,
|
|
targetFlashBorrow,
|
|
inventoryLoopGuardConfig,
|
|
rankProfile,
|
|
flashAddToPool,
|
|
)
|
|
process.exit(0)
|
|
}
|
|
|
|
if (values['mainnet-map']) {
|
|
const delta =
|
|
values['compare-deepen'] != null ? parseNum(values['compare-deepen']) : null
|
|
const m = loadMainnetCwUsdcUsdc()
|
|
printMainnetMap(
|
|
delta,
|
|
B,
|
|
Q,
|
|
values.scan ?? null,
|
|
lpFeeBps,
|
|
m?.feeBps ?? null,
|
|
!lpFeeBpsWasExplicit,
|
|
)
|
|
process.exit(0)
|
|
}
|
|
|
|
if (values.scan) {
|
|
if (B == null || Q == null) {
|
|
console.error('Error: --scan requires -B and -Q')
|
|
process.exit(1)
|
|
}
|
|
const sizes = values.scan.split(',').map((s) => parseNum(s.trim()))
|
|
console.log('trade_x\tbase_out\tvwap\trepay\tP_ext*\tPnL@ext')
|
|
for (const x of sizes) {
|
|
const { baseOut, vwap } = quoteInToBaseOut(B, Q, x, lpFeeBps)
|
|
const repay = repayUsdc(x, flashFeeBps)
|
|
const pStar = breakEvenExternalPrice(x, baseOut, flashFeeBps, exitFeeBps)
|
|
const pi =
|
|
externalPrice != null
|
|
? profit(x, baseOut, externalPrice, flashFeeBps, exitFeeBps)
|
|
: NaN
|
|
console.log(
|
|
[fmt(x), fmt(baseOut), fmt(vwap), fmt(repay), fmt(pStar), Number.isFinite(pi) ? fmt(pi) : '—'].join(
|
|
'\t',
|
|
),
|
|
)
|
|
}
|
|
process.exit(0)
|
|
}
|
|
|
|
const x = values.trade != null ? parseNum(values.trade) : null
|
|
if (B == null || Q == null || x == null) {
|
|
console.error('Error: provide -B, -Q, -x or use --examples')
|
|
process.exit(1)
|
|
}
|
|
|
|
printScenario('custom', B, Q, x, lpFeeBps, flashFeeBps, externalPrice, exitFeeBps)
|
|
}
|
|
|
|
main()
|