diff --git a/src/abis/CurveStEthPool.json b/src/abis/CurveStEthPool.json new file mode 100644 index 0000000..a6b5566 --- /dev/null +++ b/src/abis/CurveStEthPool.json @@ -0,0 +1,484 @@ +[ + { + "name": "TokenExchange", + "inputs": [ + { "type": "address", "name": "buyer", "indexed": true }, + { "type": "int128", "name": "sold_id", "indexed": false }, + { "type": "uint256", "name": "tokens_sold", "indexed": false }, + { "type": "int128", "name": "bought_id", "indexed": false }, + { "type": "uint256", "name": "tokens_bought", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "TokenExchangeUnderlying", + "inputs": [ + { "type": "address", "name": "buyer", "indexed": true }, + { "type": "int128", "name": "sold_id", "indexed": false }, + { "type": "uint256", "name": "tokens_sold", "indexed": false }, + { "type": "int128", "name": "bought_id", "indexed": false }, + { "type": "uint256", "name": "tokens_bought", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "AddLiquidity", + "inputs": [ + { "type": "address", "name": "provider", "indexed": true }, + { "type": "uint256[2]", "name": "token_amounts", "indexed": false }, + { "type": "uint256[2]", "name": "fees", "indexed": false }, + { "type": "uint256", "name": "invariant", "indexed": false }, + { "type": "uint256", "name": "token_supply", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "RemoveLiquidity", + "inputs": [ + { "type": "address", "name": "provider", "indexed": true }, + { "type": "uint256[2]", "name": "token_amounts", "indexed": false }, + { "type": "uint256[2]", "name": "fees", "indexed": false }, + { "type": "uint256", "name": "token_supply", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "RemoveLiquidityOne", + "inputs": [ + { "type": "address", "name": "provider", "indexed": true }, + { "type": "uint256", "name": "token_amount", "indexed": false }, + { "type": "uint256", "name": "coin_amount", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "RemoveLiquidityImbalance", + "inputs": [ + { "type": "address", "name": "provider", "indexed": true }, + { "type": "uint256[2]", "name": "token_amounts", "indexed": false }, + { "type": "uint256[2]", "name": "fees", "indexed": false }, + { "type": "uint256", "name": "invariant", "indexed": false }, + { "type": "uint256", "name": "token_supply", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "CommitNewAdmin", + "inputs": [ + { "type": "uint256", "name": "deadline", "indexed": true }, + { "type": "address", "name": "admin", "indexed": true } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "NewAdmin", + "inputs": [{ "type": "address", "name": "admin", "indexed": true }], + "anonymous": false, + "type": "event" + }, + { + "name": "CommitNewFee", + "inputs": [ + { "type": "uint256", "name": "deadline", "indexed": true }, + { "type": "uint256", "name": "fee", "indexed": false }, + { "type": "uint256", "name": "admin_fee", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "NewFee", + "inputs": [ + { "type": "uint256", "name": "fee", "indexed": false }, + { "type": "uint256", "name": "admin_fee", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "RampA", + "inputs": [ + { "type": "uint256", "name": "old_A", "indexed": false }, + { "type": "uint256", "name": "new_A", "indexed": false }, + { "type": "uint256", "name": "initial_time", "indexed": false }, + { "type": "uint256", "name": "future_time", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StopRampA", + "inputs": [ + { "type": "uint256", "name": "A", "indexed": false }, + { "type": "uint256", "name": "t", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "outputs": [], + "inputs": [ + { "type": "address", "name": "_owner" }, + { "type": "address[2]", "name": "_coins" }, + { "type": "address", "name": "_pool_token" }, + { "type": "uint256", "name": "_A" }, + { "type": "uint256", "name": "_fee" }, + { "type": "uint256", "name": "_admin_fee" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "name": "A", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 5289 + }, + { + "name": "A_precise", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 5251 + }, + { + "name": "balances", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [{ "type": "uint256", "name": "i" }], + "stateMutability": "view", + "type": "function", + "gas": 5076 + }, + { + "name": "get_virtual_price", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 1114301 + }, + { + "name": "calc_token_amount", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [ + { "type": "uint256[2]", "name": "amounts" }, + { "type": "bool", "name": "is_deposit" } + ], + "stateMutability": "view", + "type": "function", + "gas": 2218181 + }, + { + "name": "add_liquidity", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [ + { "type": "uint256[2]", "name": "amounts" }, + { "type": "uint256", "name": "min_mint_amount" } + ], + "stateMutability": "payable", + "type": "function", + "gas": 3484118 + }, + { + "name": "get_dy", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [ + { "type": "int128", "name": "i" }, + { "type": "int128", "name": "j" }, + { "type": "uint256", "name": "dx" } + ], + "stateMutability": "view", + "type": "function", + "gas": 2654541 + }, + { + "name": "exchange", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [ + { "type": "int128", "name": "i" }, + { "type": "int128", "name": "j" }, + { "type": "uint256", "name": "dx" }, + { "type": "uint256", "name": "min_dy" } + ], + "stateMutability": "payable", + "type": "function", + "gas": 2810134 + }, + { + "name": "remove_liquidity", + "outputs": [{ "type": "uint256[2]", "name": "" }], + "inputs": [ + { "type": "uint256", "name": "_amount" }, + { "type": "uint256[2]", "name": "_min_amounts" } + ], + "stateMutability": "nonpayable", + "type": "function", + "gas": 160545 + }, + { + "name": "remove_liquidity_imbalance", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [ + { "type": "uint256[2]", "name": "_amounts" }, + { "type": "uint256", "name": "_max_burn_amount" } + ], + "stateMutability": "nonpayable", + "type": "function", + "gas": 3519382 + }, + { + "name": "calc_withdraw_one_coin", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [ + { "type": "uint256", "name": "_token_amount" }, + { "type": "int128", "name": "i" } + ], + "stateMutability": "view", + "type": "function", + "gas": 1435 + }, + { + "name": "remove_liquidity_one_coin", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [ + { "type": "uint256", "name": "_token_amount" }, + { "type": "int128", "name": "i" }, + { "type": "uint256", "name": "_min_amount" } + ], + "stateMutability": "nonpayable", + "type": "function", + "gas": 4113806 + }, + { + "name": "ramp_A", + "outputs": [], + "inputs": [ + { "type": "uint256", "name": "_future_A" }, + { "type": "uint256", "name": "_future_time" } + ], + "stateMutability": "nonpayable", + "type": "function", + "gas": 151834 + }, + { + "name": "stop_ramp_A", + "outputs": [], + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "gas": 148595 + }, + { + "name": "commit_new_fee", + "outputs": [], + "inputs": [ + { "type": "uint256", "name": "new_fee" }, + { "type": "uint256", "name": "new_admin_fee" } + ], + "stateMutability": "nonpayable", + "type": "function", + "gas": 110431 + }, + { + "name": "apply_new_fee", + "outputs": [], + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "gas": 153115 + }, + { + "name": "revert_new_parameters", + "outputs": [], + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "gas": 21865 + }, + { + "name": "commit_transfer_ownership", + "outputs": [], + "inputs": [{ "type": "address", "name": "_owner" }], + "stateMutability": "nonpayable", + "type": "function", + "gas": 74603 + }, + { + "name": "apply_transfer_ownership", + "outputs": [], + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "gas": 116583 + }, + { + "name": "revert_transfer_ownership", + "outputs": [], + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "gas": 21955 + }, + { + "name": "withdraw_admin_fees", + "outputs": [], + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "gas": 137597 + }, + { + "name": "donate_admin_fees", + "outputs": [], + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "gas": 42144 + }, + { + "name": "kill_me", + "outputs": [], + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "gas": 37938 + }, + { + "name": "unkill_me", + "outputs": [], + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "gas": 22075 + }, + { + "name": "coins", + "outputs": [{ "type": "address", "name": "" }], + "inputs": [{ "type": "uint256", "name": "arg0" }], + "stateMutability": "view", + "type": "function", + "gas": 2160 + }, + { + "name": "admin_balances", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [{ "type": "uint256", "name": "arg0" }], + "stateMutability": "view", + "type": "function", + "gas": 2190 + }, + { + "name": "fee", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2111 + }, + { + "name": "admin_fee", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2141 + }, + { + "name": "owner", + "outputs": [{ "type": "address", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2171 + }, + { + "name": "lp_token", + "outputs": [{ "type": "address", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2201 + }, + { + "name": "initial_A", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2231 + }, + { + "name": "future_A", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2261 + }, + { + "name": "initial_A_time", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2291 + }, + { + "name": "future_A_time", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2321 + }, + { + "name": "admin_actions_deadline", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2351 + }, + { + "name": "transfer_ownership_deadline", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2381 + }, + { + "name": "future_fee", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2411 + }, + { + "name": "future_admin_fee", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2441 + }, + { + "name": "future_owner", + "outputs": [{ "type": "address", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 2471 + } +] diff --git a/src/abis/UniswapV3Quoter.json b/src/abis/UniswapV3Quoter.json new file mode 100644 index 0000000..e9adacb --- /dev/null +++ b/src/abis/UniswapV3Quoter.json @@ -0,0 +1,97 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_factory", "type": "address" }, + { "internalType": "address", "name": "_WETH9", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "WETH9", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes", "name": "path", "type": "bytes" }, + { "internalType": "uint256", "name": "amountIn", "type": "uint256" } + ], + "name": "quoteExactInput", + "outputs": [ + { "internalType": "uint256", "name": "amountOut", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "name": "quoteExactInputSingle", + "outputs": [ + { "internalType": "uint256", "name": "amountOut", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes", "name": "path", "type": "bytes" }, + { "internalType": "uint256", "name": "amountOut", "type": "uint256" } + ], + "name": "quoteExactOutput", + "outputs": [ + { "internalType": "uint256", "name": "amountIn", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "uint256", "name": "amountOut", "type": "uint256" }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "name": "quoteExactOutputSingle", + "outputs": [ + { "internalType": "uint256", "name": "amountIn", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "int256", "name": "amount0Delta", "type": "int256" }, + { "internalType": "int256", "name": "amount1Delta", "type": "int256" }, + { "internalType": "bytes", "name": "path", "type": "bytes" } + ], + "name": "uniswapV3SwapCallback", + "outputs": [], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abis/wstETH.json b/src/abis/wstETH.json new file mode 100644 index 0000000..fede488 --- /dev/null +++ b/src/abis/wstETH.json @@ -0,0 +1,252 @@ +[ + { + "inputs": [ + { "internalType": "contract IStETH", "name": "_stETH", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_wstETHAmount", "type": "uint256" } + ], + "name": "getStETHByWstETH", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_stETHAmount", "type": "uint256" } + ], + "name": "getWstETHByStETH", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stETH", + "outputs": [ + { "internalType": "contract IStETH", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stEthPerToken", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokensPerStEth", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_wstETHAmount", "type": "uint256" } + ], + "name": "unwrap", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_stETHAmount", "type": "uint256" } + ], + "name": "wrap", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/src/js/tasks/lido.js b/src/js/tasks/lido.js index 5ab05c8..5755656 100644 --- a/src/js/tasks/lido.js +++ b/src/js/tasks/lido.js @@ -1,5 +1,12 @@ const { formatUnits, parseUnits, MaxInt256 } = require("ethers"); +const addresses = require("../utils/addresses"); +const { + logArmPrices, + log1InchPrices, + logCurvePrices, + logUniswapSpotPrices, +} = require("./markets"); const { getBlock } = require("../utils/block"); const { getSigner } = require("../utils/signers"); const { logTxDetails } = require("../utils/txLogger"); @@ -90,9 +97,9 @@ const submitLido = async ({ amount }) => { await logTxDetails(tx, "submit"); }; -const snapLido = async ({ block }) => { +const snapLido = async ({ amount, block, curve, oneInch, uniswap }) => { const blockTag = await getBlock(block); - console.log(`\nLiquidity`); + const pair = "stETH/ETH"; const armAddress = await parseAddress("LIDO_ARM"); const lidoARM = await ethers.getContractAt("LidoARM", armAddress); @@ -102,7 +109,40 @@ const snapLido = async ({ block }) => { capManagerAddress ); - await logRates(lidoARM, blockTag); + const ammPrices = await logArmPrices(lidoARM, blockTag); + + if (oneInch) { + await log1InchPrices(amount, ammPrices); + } + + if (curve) { + await logCurvePrices( + { + blockTag, + amount, + pair, + poolName: "Old", + poolAddress: addresses.mainnet.CurveStEthPool, + }, + ammPrices + ); + + await logCurvePrices( + { + blockTag, + amount, + pair, + poolName: "NextGen", + poolAddress: addresses.mainnet.CurveNgStEthPool, + }, + ammPrices + ); + } + + if (uniswap) { + await logUniswapSpotPrices({ blockTag, pair, amount }, ammPrices); + } + const { totalAssets, totalSupply, liquidityWeth } = await logAssets( lidoARM, blockTag @@ -178,68 +218,30 @@ const logAssets = async (arm, blockTag) => { console.log(`\nAssets`); console.log( - `${formatUnits(liquidityWeth, 18)} WETH ${formatUnits(wethPercent, 2)}%` + `${formatUnits(liquidityWeth, 18).padEnd(23)} WETH ${formatUnits( + wethPercent, + 2 + )}%` ); console.log( - `${formatUnits(liquiditySteth, 18)} stETH ${formatUnits(oethPercent, 2)}%` + `${formatUnits(liquiditySteth, 18).padEnd(23)} stETH ${formatUnits( + oethPercent, + 2 + )}%` ); console.log( - `${formatUnits( - liquidityLidoWithdraws, - 18 - )} Lido withdrawal requests ${formatUnits(stethWithdrawsPercent, 2)}%` + `${formatUnits(liquidityLidoWithdraws, 18).padEnd( + 23 + )} Lido withdraw ${formatUnits(stethWithdrawsPercent, 2)}%` ); - console.log(`${formatUnits(total, 18)} total WETH and stETH`); - console.log(`${formatUnits(totalAssets, 18)} total assets`); - console.log(`${formatUnits(totalSupply, 18)} total supply`); - console.log(`${formatUnits(assetPerShare, 18)} asset per share`); + console.log(`${formatUnits(total, 18).padEnd(23)} total WETH and stETH`); + console.log(`${formatUnits(totalAssets, 18).padEnd(23)} total assets`); + console.log(`${formatUnits(totalSupply, 18).padEnd(23)} total supply`); + console.log(`${formatUnits(assetPerShare, 18).padEnd(23)} asset per share`); return { totalAssets, totalSupply, liquidityWeth }; }; -const logRates = async (arm, blockTag) => { - console.log(`\nPrices`); - // The rate of 1 WETH for stETH to 36 decimals from the perspective of the AMM. ie WETH/stETH - // from the trader's perspective, this is the stETH/WETH buy price - const OWethStEthRate = await arm.traderate0({ blockTag }); - console.log(`traderate0: ${formatUnits(OWethStEthRate, 36)} WETH/stETH`); - - // convert from WETH/stETH rate with 36 decimals to stETH/WETH rate with 18 decimals - const sellPrice = BigInt(1e54) / BigInt(OWethStEthRate); - - // The rate of 1 stETH for WETH to 36 decimals. ie stETH/WETH - const OStEthWethRate = await arm.traderate1({ blockTag }); - console.log(`traderate1: ${formatUnits(OStEthWethRate, 36)} stETH/WETH`); - // Convert back to 18 decimals - const buyPrice = BigInt(OStEthWethRate) / BigInt(1e18); - - const midPrice = (sellPrice + buyPrice) / 2n; - - const crossPrice = await arm.crossPrice({ blockTag }); - - console.log(`sell : ${formatUnits(sellPrice, 18).padEnd(20)} stETH/WETH`); - if (crossPrice > sellPrice) { - console.log(`cross : ${formatUnits(crossPrice, 36).padEnd(20)} stETH/WETH`); - console.log(`mid : ${formatUnits(midPrice, 18).padEnd(20)} stETH/WETH`); - } else { - console.log(`mid : ${formatUnits(midPrice, 18).padEnd(20)} stETH/WETH`); - console.log(`cross : ${formatUnits(crossPrice, 18).padEnd(20)} stETH/WETH`); - } - console.log(`buy : ${formatUnits(buyPrice, 18).padEnd(20)} stETH/WETH`); - - const spread = BigInt(sellPrice) - BigInt(buyPrice); - // Origin rates are to 36 decimals - console.log(`spread: ${formatUnits(spread, 14)} bps`); - - return { - buyPrice: sellPrice, - sellPrice: buyPrice, - midPrice, - crossPrice, - spread, - }; -}; - const swapLido = async ({ from, to, amount }) => { if (from && to) { throw new Error( diff --git a/src/js/tasks/markets.js b/src/js/tasks/markets.js new file mode 100644 index 0000000..a8cb059 --- /dev/null +++ b/src/js/tasks/markets.js @@ -0,0 +1,193 @@ +const { formatUnits } = require("ethers"); + +const { get1InchPrices } = require("../utils/1Inch"); +const { getCurvePrices } = require("../utils/curve"); +const { getUniswapV3SpotPrices } = require("../utils/uniswap"); + +const log = require("../utils/logger")("task:markets"); + +const logArmPrices = async (arm, blockTag) => { + console.log(`\nARM Prices`); + // The rate of 1 WETH for stETH to 36 decimals from the perspective of the AMM. ie WETH/stETH + // from the trader's perspective, this is the stETH/WETH buy price + const OWethStEthRate = await arm.traderate0({ blockTag }); + console.log(`traderate0: ${formatUnits(OWethStEthRate, 36)} WETH/stETH`); + + // convert from WETH/stETH rate with 36 decimals to stETH/WETH rate with 18 decimals + const sellPrice = BigInt(1e54) / BigInt(OWethStEthRate); + + // The rate of 1 stETH for WETH to 36 decimals. ie stETH/WETH + const OStEthWethRate = await arm.traderate1({ blockTag }); + console.log(`traderate1: ${formatUnits(OStEthWethRate, 36)} stETH/WETH`); + // Convert back to 18 decimals + const buyPrice = BigInt(OStEthWethRate) / BigInt(1e18); + + const midPrice = (sellPrice + buyPrice) / 2n; + + const crossPrice = await arm.crossPrice({ blockTag }); + + console.log(`sell : ${formatUnits(sellPrice, 18).padEnd(20)} stETH/WETH`); + if (crossPrice > sellPrice) { + console.log( + `cross : ${formatUnits(crossPrice, 36).padEnd(20)} stETH/WETH` + ); + console.log(`mid : ${formatUnits(midPrice, 18).padEnd(20)} stETH/WETH`); + } else { + console.log(`mid : ${formatUnits(midPrice, 18).padEnd(20)} stETH/WETH`); + console.log( + `cross : ${formatUnits(crossPrice, 18).padEnd(20)} stETH/WETH` + ); + } + console.log(`buy : ${formatUnits(buyPrice, 18).padEnd(20)} stETH/WETH`); + + const spread = BigInt(sellPrice) - BigInt(buyPrice); + // Origin rates are to 36 decimals + console.log(`spread : ${formatUnits(spread, 14)} bps`); + + return { + buyPrice: sellPrice, + sellPrice: buyPrice, + midPrice, + crossPrice, + spread, + }; +}; + +const log1InchPrices = async (amount, ammPrices) => { + const oneInch = await get1InchPrices(amount); + + log(`buy ${formatUnits(oneInch.buyToAmount)} stETH for ${amount} WETH`); + log(`sell ${amount} stETH for ${formatUnits(oneInch.sellToAmount)} WETH`); + + console.log(`\n1Inch prices for swap size ${amount}`); + const buyRateDiff = oneInch.buyPrice - ammPrices.buyPrice; + console.log( + `buy : ${formatUnits(oneInch.buyPrice, 18).padEnd( + 20 + )} stETH/WETH, diff ${formatUnits(buyRateDiff, 14).padEnd( + 17 + )} bps to ARM, ${oneInch.buyGas.toLocaleString()} gas` + ); + + const midRateDiff = oneInch.midPrice - ammPrices.midPrice; + console.log( + `mid : ${formatUnits(oneInch.midPrice, 18).padEnd( + 20 + )} stETH/WETH, diff ${formatUnits(midRateDiff, 14).padEnd(17)} bps to ARM` + ); + + const sellRateDiff = oneInch.sellPrice - ammPrices.sellPrice; + console.log( + `sell : ${formatUnits(oneInch.sellPrice, 18).padEnd( + 20 + )} stETH/WETH, diff ${formatUnits(sellRateDiff, 14).padEnd( + 17 + )} bps to ARM, ${oneInch.sellGas.toLocaleString()} gas` + ); + console.log(`spread : ${formatUnits(oneInch.spread, 14)} bps`); + + console.log(`buy path for stETH/WETH`); + log1InchProtocols(oneInch.buyQuote); + + console.log(`sell path for stETH/WETH`); + log1InchProtocols(oneInch.sellQuote); + + return oneInch; +}; + +const log1InchProtocols = (sellQuote) => { + // TODO need to better handle + sellQuote.protocols.forEach((p1) => { + p1.forEach((p2) => { + p2.forEach((p3) => { + console.log( + `${p3.part.toString().padEnd(3)}% ${p3.name.padEnd(12)} ${ + p3.fromTokenAddress + } -> ${p3.toTokenAddress}` + ); + }); + }); + }); +}; + +const logCurvePrices = async (options, ammPrices) => { + const { amount, pair, poolName } = options; + + const curve = await getCurvePrices(options); + const buyRateDiff = curve.buyPrice - ammPrices.buyPrice; + const midRateDiff = curve.midPrice - ammPrices.midPrice; + const sellRateDiff = curve.sellPrice - ammPrices.sellPrice; + + log(`buy ${formatUnits(curve.buyToAmount)} stETH for ${amount} WETH`); + log(`sell ${amount} stETH for ${formatUnits(curve.sellToAmount)} WETH`); + + console.log(`\n${poolName} Curve prices for swap size ${amount}`); + console.log( + `buy : ${formatUnits(curve.buyPrice, 18).padEnd( + 20 + )} ${pair}, diff ${formatUnits(buyRateDiff, 14).padEnd( + 17 + )} bps to ARM, ${curve.buyGas.toLocaleString()} gas` + ); + console.log( + `mid : ${formatUnits(curve.midPrice, 18).padEnd( + 20 + )} ${pair}, diff ${formatUnits(midRateDiff, 14).padEnd(17)} bps to ARM` + ); + console.log( + `sell : ${formatUnits(curve.sellPrice, 18).padEnd( + 20 + )} ${pair}, diff ${formatUnits(sellRateDiff, 1).padEnd( + 17 + )} bps to ARM, ${curve.sellGas.toLocaleString()} gas` + ); + console.log(`spread : ${formatUnits(curve.spread, 14)} bps`); + + return curve; +}; + +const logUniswapSpotPrices = async (options, ammPrices) => { + const { amount, pair } = options; + const uniswap = await getUniswapV3SpotPrices(options); + const buyRateDiff = uniswap.buyPrice - ammPrices.buyPrice; + const midRateDiff = uniswap.midPrice - ammPrices.midPrice; + const sellRateDiff = uniswap.sellPrice - ammPrices.sellPrice; + + log(`buy ${formatUnits(uniswap.buyToAmount)} stETH for ${amount} WETH`); + log(`sell ${amount} stETH for ${formatUnits(uniswap.sellToAmount)} WETH`); + + console.log( + `\nwstETH/ETH 0.01% Uniswap V3 spot prices for swap size ${amount}` + ); + console.log( + `buy : ${formatUnits(uniswap.buyPrice, 18).padEnd( + 20 + )} ${pair}, diff ${formatUnits( + buyRateDiff, + 14 + )} bps to ARM, ${uniswap.buyGas.toLocaleString()} gas` + ); + console.log( + `mid : ${formatUnits(uniswap.midPrice, 18).padEnd( + 20 + )} ${pair}, diff ${formatUnits(midRateDiff, 14)} bps to ARM` + ); + console.log( + `sell : ${formatUnits(uniswap.sellPrice, 18).padEnd( + 20 + )} ${pair}, diff ${formatUnits( + sellRateDiff, + 14 + )} bps to ARM, ${uniswap.sellGas.toLocaleString()} gas` + ); + console.log(`spread : ${formatUnits(uniswap.spread, 14)} bps`); + + return uniswap; +}; + +module.exports = { + log1InchPrices, + logArmPrices, + logCurvePrices, + logUniswapSpotPrices, +}; diff --git a/src/js/tasks/tasks.js b/src/js/tasks/tasks.js index 79c6e66..0187deb 100644 --- a/src/js/tasks/tasks.js +++ b/src/js/tasks/tasks.js @@ -608,6 +608,10 @@ subtask("snapLido", "Take a snapshot of the Lido ARM") undefined, types.int ) + .addOptionalParam("amount", "Swap quantity", 100, types.int) + .addOptionalParam("oneInch", "Include 1Inch prices", true, types.boolean) + .addOptionalParam("curve", "Include Curve prices", true, types.boolean) + .addOptionalParam("uniswap", "Include Uniswap V3 prices", true, types.boolean) .setAction(snapLido); task("snapLido").setAction(async (_, __, runSuper) => { return runSuper(); diff --git a/src/js/utils/1Inch.js b/src/js/utils/1Inch.js new file mode 100644 index 0000000..77ad4d4 --- /dev/null +++ b/src/js/utils/1Inch.js @@ -0,0 +1,122 @@ +const axios = require("axios"); +const { parseUnits } = require("ethers"); + +const addresses = require("../utils/addresses"); +const { sleep } = require("../utils/time"); + +const log = require("./logger")("utils:1inch"); + +const ONEINCH_API_ENDPOINT = "https://api.1inch.dev/swap/v5.2/1/quote"; + +/** + * Gets a swap quote from 1Inch's V5.2 swap API + * @param fromAsset The address of the asset to swap from. + * @param toAsset The address of the asset to swap to. + * @param fromAmount The unit amount of fromAsset to swap. eg 1.1 WETH = 1.1e18 + * See https://docs.1inch.io/docs/aggregation-protocol/api/swagger + */ +const get1InchSwapQuote = async ({ fromAsset, toAsset, fromAmount }) => { + const apiKey = process.env.ONEINCH_API_KEY; + if (!apiKey) { + throw Error( + "ONEINCH_API_KEY environment variable not set. Visit the 1Inch Dev Portal https://portal.1inch.dev/" + ); + } + + const params = { + src: fromAsset, + dst: toAsset, + amount: fromAmount.toString(), + allowPartialFill: true, + disableEstimate: true, + includeProtocols: true, + includeGas: true, + includeTokensInfo: false, + }; + log("swap API params: ", params); + + let retries = 3; + + while (retries > 0) { + try { + const response = await axios.get(ONEINCH_API_ENDPOINT, { + params, + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + if (!response.data.toAmount) { + console.error(response.data); + throw Error("response is missing toAmount"); + } + + log("swap API response data: %j", response.data); + + return response.data; + } catch (err) { + if (err.response) { + console.error("Response data : ", err.response.data); + console.error("Response status: ", err.response.status); + console.error("Response status: ", err.response.statusText); + } + if (err.response?.status == 429) { + retries = retries - 1; + console.error( + `Failed to get a 1Inch quote. Will try again in 2 seconds with ${retries} retries left` + ); + // Wait for 2s before next try + await new Promise((r) => setTimeout(r, 2000)); + continue; + } + throw Error(`Call to 1Inch swap quote API failed: ${err.message}`); + } + } + + throw Error(`Call to 1Inch swap quote API failed: Rate-limited`); +}; + +const get1InchPrices = async (amount) => { + + const amountBI = parseUnits(amount.toString(), 18); + + const buyQuote = await get1InchSwapQuote({ + fromAsset: addresses.mainnet.WETH, + toAsset: addresses.mainnet.stETH, + fromAmount: amountBI, // WETH amount + }); + // stETH buy amount + const buyToAmount = BigInt(buyQuote.toAmount); + // stETH/ETH rate = ETH amount / stETH amount + const buyPrice = (amountBI * BigInt(1e18)) / buyToAmount; + + await sleep(800); + + const sellQuote = await get1InchSwapQuote({ + fromAsset: addresses.mainnet.stETH, + toAsset: addresses.mainnet.WETH, + fromAmount: amountBI, // stETH amount + }); + // WETH sell amount + const sellToAmount = BigInt(sellQuote.toAmount); + // stETH/WETH rate = WETH amount / stETH amount + const sellPrice = (sellToAmount * BigInt(1e18)) / amountBI; + + const midPrice = (buyPrice + sellPrice) / 2n; + const spread = buyPrice - sellPrice; + + return { + buyQuote, + buyToAmount, + buyPrice, + buyGas: buyQuote.gas, + sellQuote, + sellToAmount, + sellPrice, + sellGas: sellQuote.gas, + midPrice, + spread, + }; +}; + +module.exports = { get1InchSwapQuote, get1InchPrices }; diff --git a/src/js/utils/addresses.js b/src/js/utils/addresses.js index 5bcae17..0695483 100644 --- a/src/js/utils/addresses.js +++ b/src/js/utils/addresses.js @@ -17,8 +17,18 @@ addresses.mainnet.OETHVaultProxy = "0x39254033945aa2e4809cc2977e7087bee48bd7ab"; // Tokens addresses.mainnet.WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; - +addresses.mainnet.stETH = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"; +addresses.mainnet.wstETH = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"; addresses.mainnet.OethARM = "0x6bac785889A4127dB0e0CeFEE88E0a9F1Aaf3cC7"; +// AMMs +addresses.mainnet.CurveStEthPool = "0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"; +addresses.mainnet.CurveNgStEthPool = + "0x21e27a5e5513d6e65c4f830167390997aa84843a"; +addresses.mainnet.UniswapV3Quoter = + "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6"; +addresses.mainnet.UniswapV3stETHWETHPool = + "0x109830a1AAaD605BbF02a9dFA7B0B92EC2FB7dAa"; + module.exports = addresses; diff --git a/src/js/utils/curve.js b/src/js/utils/curve.js new file mode 100644 index 0000000..184f18a --- /dev/null +++ b/src/js/utils/curve.js @@ -0,0 +1,57 @@ +const { parseUnits } = require("ethers"); + +const curvePoolAbi = require("../../abis/CurveStEthPool.json"); + +const getCurvePrices = async ({ amount, poolAddress, blockTag }) => { + const pool = await ethers.getContractAt(curvePoolAbi, poolAddress); + + const amountBI = parseUnits(amount.toString(), 18); + + // Swap ETH for stETH + const buyToAmount = await pool["get_dy(int128,int128,uint256)"]( + 0, + 1, + amountBI, + { blockTag } + ); + const buyGas = await pool["get_dy(int128,int128,uint256)"].estimateGas( + 0, + 1, + amountBI, + { blockTag } + ); + // stETH/ETH rate = ETH amount / stETH amount + const buyPrice = (amountBI * BigInt(1e18)) / buyToAmount; + + // Swap stETH for ETH + const sellToAmount = await pool["get_dy(int128,int128,uint256)"]( + 1, + 0, + amountBI, + { blockTag } + ); + const sellGas = await pool["get_dy(int128,int128,uint256)"].estimateGas( + 1, + 0, + amountBI, + { blockTag } + ); + // stETH/WETH rate = WETH amount / stETH amount + const sellPrice = (sellToAmount * BigInt(1e18)) / amountBI; + + const midPrice = (buyPrice + sellPrice) / 2n; + const spread = buyPrice - sellPrice; + + return { + buyToAmount, + buyPrice, + buyGas, + sellToAmount, + sellPrice, + sellGas, + midPrice, + spread, + }; +}; + +module.exports = { getCurvePrices }; diff --git a/src/js/utils/uniswap.js b/src/js/utils/uniswap.js new file mode 100644 index 0000000..8655dec --- /dev/null +++ b/src/js/utils/uniswap.js @@ -0,0 +1,96 @@ +const { parseUnits } = require("ethers"); +const { ethers } = require("ethers"); + +const quoterAbi = require("../../abis/UniswapV3Quoter.json"); +const wstEthAbi = require("../../abis/wstETH.json"); +const addresses = require("./addresses"); +const { getSigner } = require("./signers"); + +const log = require("../utils/logger")("task:uniswap"); + +const getUniswapV3SpotPrices = async ({ amount, blockTag }) => { + const signer = await getSigner(); + const quoter = new ethers.Contract( + addresses.mainnet.UniswapV3Quoter, + quoterAbi, + signer + ); + + const wstEth = new ethers.Contract( + addresses.mainnet.wstETH, + wstEthAbi, + signer + ); + + const amountBI = parseUnits(amount.toString(), 18); + + // Swap WETH for stETH + const wstEthAmount = await quoter + .connect(signer) + .quoteExactInputSingle.staticCall( + addresses.mainnet.WETH, + addresses.mainnet.wstETH, + 100, + amountBI, + 0, + { blockTag } + ); + const buyToAmount = await wstEth.getStETHByWstETH(wstEthAmount); + log(`buyToAmount: ${buyToAmount}`); + const buyGas = await quoter + .connect(signer) + .quoteExactInputSingle.estimateGas( + addresses.mainnet.WETH, + addresses.mainnet.wstETH, + 100, + amountBI, + 0, + { blockTag } + ); + // stETH/ETH rate = ETH amount / stETH amount + const buyPrice = (amountBI * BigInt(1e18)) / buyToAmount; + + // Swap stETH for WETH + // Convert stETH to wstETH + const wstETHAmount = await wstEth.getWstETHByStETH(amountBI); + log(`wstETHAmount: ${wstETHAmount} ${typeof wstETHAmount}`); + // Convert wstETH to WETH + const sellToAmount = await quoter + .connect(signer) + .quoteExactInputSingle.staticCall( + addresses.mainnet.wstETH, + addresses.mainnet.WETH, + 100, + wstETHAmount, + 0, + { blockTag } + ); + const sellGas = await quoter + .connect(signer) + .quoteExactInputSingle.estimateGas( + addresses.mainnet.wstETH, + addresses.mainnet.WETH, + 100, + amountBI, + 0, + { blockTag } + ); + // stETH/WETH rate = WETH amount / stETH amount + const sellPrice = (sellToAmount * BigInt(1e18)) / amountBI; + + const midPrice = (buyPrice + sellPrice) / 2n; + const spread = buyPrice - sellPrice; + + return { + buyToAmount, + buyPrice, + buyGas, + sellToAmount, + sellPrice, + sellGas, + midPrice, + spread, + }; +}; + +module.exports = { getUniswapV3SpotPrices };