diff --git a/.env.example b/.env.example index f976da3..a27d308 100644 --- a/.env.example +++ b/.env.example @@ -70,9 +70,15 @@ SLACK_AUTH_TOKEN= # Descriptive name for this bot, for logging purposes SLACK_REBALANCE_BOT_NAME= -# ============== +# =============== # ==== Balmy ==== -# ============== +# =============== BALMY_SLIPPAGE_PERCENTAGE= -LIFI_API_KEY= \ No newline at end of file +LIFI_API_KEY= + +# ============= +# ==== DEX ==== +# ============= + +DEX_SLIPPAGE_BPS= \ No newline at end of file diff --git a/abis/DutchAuctionRebalancer.ts b/abis/DutchAuctionRebalancer.ts index 159c761..bbee6fa 100644 --- a/abis/DutchAuctionRebalancer.ts +++ b/abis/DutchAuctionRebalancer.ts @@ -1,4 +1,4 @@ -export const DutchAuctionRebalancerAbi = [ +const DutchAuctionRebalancerBaseAbi = [ { "type": "constructor", "inputs": [ @@ -321,5 +321,25 @@ export const DutchAuctionRebalancerAbi = [ "type": "error", "name": "Unauthorized", "inputs": [] + }, +] as const; + +export const DutchAuctionRebalancerAbi = [ + ...DutchAuctionRebalancerBaseAbi, + { + "type": "error", + "name": "AuctionNotValid", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidLeverageTokenStateAfterRebalance", + "inputs": [ + { + "name": "leverageToken", + "type": "address", + "internalType": "address" + }, + ] } ] as const; diff --git a/abis/PendleMarketV3.ts b/abis/PendleMarketV3.ts new file mode 100644 index 0000000..d325dbe --- /dev/null +++ b/abis/PendleMarketV3.ts @@ -0,0 +1,453 @@ +const PendleMarketV3Abi = [ + { + inputs: [ + { internalType: "address", name: "_PT", type: "address" }, + { internalType: "int256", name: "_scalarRoot", type: "int256" }, + { internalType: "int256", name: "_initialAnchor", type: "int256" }, + { internalType: "uint80", name: "_lnFeeRateRoot", type: "uint80" }, + { internalType: "address", name: "_vePendle", type: "address" }, + { internalType: "address", name: "_gaugeController", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [{ internalType: "int256", name: "exchangeRate", type: "int256" }], + name: "MarketExchangeRateBelowOne", + type: "error", + }, + { inputs: [], name: "MarketExpired", type: "error" }, + { + inputs: [ + { internalType: "int256", name: "currentAmount", type: "int256" }, + { internalType: "int256", name: "requiredAmount", type: "int256" }, + ], + name: "MarketInsufficientPtForTrade", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "actualBalance", type: "uint256" }, + { internalType: "uint256", name: "requiredBalance", type: "uint256" }, + ], + name: "MarketInsufficientPtReceived", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "actualBalance", type: "uint256" }, + { internalType: "uint256", name: "requiredBalance", type: "uint256" }, + ], + name: "MarketInsufficientSyReceived", + type: "error", + }, + { inputs: [], name: "MarketProportionMustNotEqualOne", type: "error" }, + { + inputs: [ + { internalType: "int256", name: "proportion", type: "int256" }, + { internalType: "int256", name: "maxProportion", type: "int256" }, + ], + name: "MarketProportionTooHigh", + type: "error", + }, + { + inputs: [{ internalType: "int256", name: "rateScalar", type: "int256" }], + name: "MarketRateScalarBelowZero", + type: "error", + }, + { + inputs: [{ internalType: "int256", name: "scalarRoot", type: "int256" }], + name: "MarketScalarRootBelowZero", + type: "error", + }, + { inputs: [], name: "MarketZeroAmountsInput", type: "error" }, + { inputs: [], name: "MarketZeroAmountsOutput", type: "error" }, + { inputs: [], name: "MarketZeroLnImpliedRate", type: "error" }, + { + inputs: [ + { internalType: "int256", name: "totalPt", type: "int256" }, + { internalType: "int256", name: "totalAsset", type: "int256" }, + ], + name: "MarketZeroTotalPtOrTotalAsset", + type: "error", + }, + { + 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: "receiverSy", type: "address" }, + { indexed: true, internalType: "address", name: "receiverPt", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpBurned", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netPtOut", type: "uint256" }, + ], + name: "Burn", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "uint16", name: "observationCardinalityNextOld", type: "uint16" }, + { indexed: false, internalType: "uint16", name: "observationCardinalityNextNew", type: "uint16" }, + ], + name: "IncreaseObservationCardinalityNext", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpMinted", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyUsed", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netPtUsed", type: "uint256" }, + ], + name: "Mint", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "user", type: "address" }, + { indexed: false, internalType: "uint256[]", name: "rewardsOut", type: "uint256[]" }, + ], + name: "RedeemRewards", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "int256", name: "netPtOut", type: "int256" }, + { indexed: false, internalType: "int256", name: "netSyOut", type: "int256" }, + { indexed: false, internalType: "uint256", name: "netSyFee", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyToReserve", type: "uint256" }, + ], + name: "Swap", + 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", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "uint256", name: "timestamp", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "lnLastImpliedRate", type: "uint256" }, + ], + name: "UpdateImpliedRate", + type: "event", + }, + { + inputs: [], + name: "_storage", + outputs: [ + { internalType: "int128", name: "totalPt", type: "int128" }, + { internalType: "int128", name: "totalSy", type: "int128" }, + { internalType: "uint96", name: "lastLnImpliedRate", type: "uint96" }, + { internalType: "uint16", name: "observationIndex", type: "uint16" }, + { internalType: "uint16", name: "observationCardinality", type: "uint16" }, + { internalType: "uint16", name: "observationCardinalityNext", type: "uint16" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "activeBalance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + 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: [ + { internalType: "address", name: "receiverSy", type: "address" }, + { internalType: "address", name: "receiverPt", type: "address" }, + { internalType: "uint256", name: "netLpToBurn", type: "uint256" }, + ], + name: "burn", + outputs: [ + { internalType: "uint256", name: "netSyOut", type: "uint256" }, + { internalType: "uint256", name: "netPtOut", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "expiry", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "factory", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getNonOverrideLnFeeRateRoot", + outputs: [{ internalType: "uint80", name: "", type: "uint80" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getRewardTokens", + outputs: [{ internalType: "address[]", name: "", type: "address[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint16", name: "cardinalityNext", type: "uint16" }], + name: "increaseObservationsCardinalityNext", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "isExpired", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "lastRewardBlock", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "netSyDesired", type: "uint256" }, + { internalType: "uint256", name: "netPtDesired", type: "uint256" }, + ], + name: "mint", + outputs: [ + { internalType: "uint256", name: "netLpOut", type: "uint256" }, + { internalType: "uint256", name: "netSyUsed", type: "uint256" }, + { internalType: "uint256", name: "netPtUsed", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "observations", + outputs: [ + { internalType: "uint32", name: "blockTimestamp", type: "uint32" }, + { internalType: "uint216", name: "lnImpliedRateCumulative", type: "uint216" }, + { internalType: "bool", name: "initialized", type: "bool" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint32[]", name: "secondsAgos", type: "uint32[]" }], + name: "observe", + outputs: [{ internalType: "uint216[]", name: "lnImpliedRateCumulative", type: "uint216[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "router", type: "address" }], + name: "readState", + outputs: [ + { + components: [ + { internalType: "int256", name: "totalPt", type: "int256" }, + { internalType: "int256", name: "totalSy", type: "int256" }, + { internalType: "int256", name: "totalLp", type: "int256" }, + { internalType: "address", name: "treasury", type: "address" }, + { internalType: "int256", name: "scalarRoot", type: "int256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint256", name: "lnFeeRateRoot", type: "uint256" }, + { internalType: "uint256", name: "reserveFeePercent", type: "uint256" }, + { internalType: "uint256", name: "lastLnImpliedRate", type: "uint256" }, + ], + internalType: "struct MarketState", + name: "market", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "readTokens", + outputs: [ + { internalType: "contract IStandardizedYield", name: "_SY", type: "address" }, + { internalType: "contract IPPrincipalToken", name: "_PT", type: "address" }, + { internalType: "contract IPYieldToken", name: "_YT", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "user", type: "address" }], + name: "redeemRewards", + outputs: [{ internalType: "uint256[]", name: "", type: "uint256[]" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "rewardState", + outputs: [ + { internalType: "uint128", name: "index", type: "uint128" }, + { internalType: "uint128", name: "lastBalance", type: "uint128" }, + ], + stateMutability: "view", + type: "function", + }, + { inputs: [], name: "skim", outputs: [], stateMutability: "nonpayable", type: "function" }, + { + inputs: [ + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "exactPtIn", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "swapExactPtForSy", + outputs: [ + { internalType: "uint256", name: "netSyOut", type: "uint256" }, + { internalType: "uint256", name: "netSyFee", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "exactPtOut", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "swapSyForExactPt", + outputs: [ + { internalType: "uint256", name: "netSyIn", type: "uint256" }, + { internalType: "uint256", name: "netSyFee", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalActiveSupply", + 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: "to", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + ], + name: "userReward", + outputs: [ + { internalType: "uint128", name: "index", type: "uint128" }, + { internalType: "uint128", name: "accrued", type: "uint128" }, + ], + stateMutability: "view", + type: "function", + }, +] as const; + +export default PendleMarketV3Abi; \ No newline at end of file diff --git a/abis/PendleRouter.ts b/abis/PendleRouter.ts new file mode 100644 index 0000000..76323c2 --- /dev/null +++ b/abis/PendleRouter.ts @@ -0,0 +1,842 @@ +const PendleRouterAbi = [ + { + inputs: [{ internalType: "int256", name: "exchangeRate", type: "int256" }], + name: "MarketExchangeRateBelowOne", + type: "error", + }, + { inputs: [], name: "MarketExpired", type: "error" }, + { inputs: [], name: "MarketProportionMustNotEqualOne", type: "error" }, + { + inputs: [ + { internalType: "int256", name: "proportion", type: "int256" }, + { internalType: "int256", name: "maxProportion", type: "int256" }, + ], + name: "MarketProportionTooHigh", + type: "error", + }, + { + inputs: [{ internalType: "int256", name: "rateScalar", type: "int256" }], + name: "MarketRateScalarBelowZero", + type: "error", + }, + { + inputs: [ + { internalType: "int256", name: "totalPt", type: "int256" }, + { internalType: "int256", name: "totalAsset", type: "int256" }, + ], + name: "MarketZeroTotalPtOrTotalAsset", + type: "error", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netSyUsed", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netPtUsed", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netLpOut", type: "uint256" }, + ], + name: "AddLiquidityDualSyAndPt", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "tokenIn", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netTokenUsed", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netPtUsed", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netLpOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + name: "AddLiquidityDualTokenAndPt", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netPtIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netLpOut", type: "uint256" }, + ], + name: "AddLiquiditySinglePt", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netSyIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netLpOut", type: "uint256" }, + ], + name: "AddLiquiditySingleSy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netSyIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyMintPy", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netLpOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netYtOut", type: "uint256" }, + ], + name: "AddLiquiditySingleSyKeepYt", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "token", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netTokenIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netLpOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + name: "AddLiquiditySingleToken", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "token", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netTokenIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netLpOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netYtOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyMintPy", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + name: "AddLiquiditySingleTokenKeepYt", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpIn", type: "uint256" }, + { + components: [ + { internalType: "uint256", name: "netPtFromRemove", type: "uint256" }, + { internalType: "uint256", name: "netSyFromRemove", type: "uint256" }, + { internalType: "uint256", name: "netPtRedeem", type: "uint256" }, + { internalType: "uint256", name: "netSyFromRedeem", type: "uint256" }, + { internalType: "uint256", name: "totalSyOut", type: "uint256" }, + ], + indexed: false, + internalType: "struct ExitPostExpReturnParams", + name: "params", + type: "tuple", + }, + ], + name: "ExitPostExpToSy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "token", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "totalTokenOut", type: "uint256" }, + { + components: [ + { internalType: "uint256", name: "netPtFromRemove", type: "uint256" }, + { internalType: "uint256", name: "netSyFromRemove", type: "uint256" }, + { internalType: "uint256", name: "netPtRedeem", type: "uint256" }, + { internalType: "uint256", name: "netSyFromRedeem", type: "uint256" }, + { internalType: "uint256", name: "totalSyOut", type: "uint256" }, + ], + indexed: false, + internalType: "struct ExitPostExpReturnParams", + name: "params", + type: "tuple", + }, + ], + name: "ExitPostExpToToken", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpIn", type: "uint256" }, + { + components: [ + { internalType: "uint256", name: "netPtFromRemove", type: "uint256" }, + { internalType: "uint256", name: "netSyFromRemove", type: "uint256" }, + { internalType: "uint256", name: "netPyRedeem", type: "uint256" }, + { internalType: "uint256", name: "netSyFromRedeem", type: "uint256" }, + { internalType: "uint256", name: "netPtSwap", type: "uint256" }, + { internalType: "uint256", name: "netYtSwap", type: "uint256" }, + { internalType: "uint256", name: "netSyFromSwap", type: "uint256" }, + { internalType: "uint256", name: "netSyFee", type: "uint256" }, + { internalType: "uint256", name: "totalSyOut", type: "uint256" }, + ], + indexed: false, + internalType: "struct ExitPreExpReturnParams", + name: "params", + type: "tuple", + }, + ], + name: "ExitPreExpToSy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "token", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "totalTokenOut", type: "uint256" }, + { + components: [ + { internalType: "uint256", name: "netPtFromRemove", type: "uint256" }, + { internalType: "uint256", name: "netSyFromRemove", type: "uint256" }, + { internalType: "uint256", name: "netPyRedeem", type: "uint256" }, + { internalType: "uint256", name: "netSyFromRedeem", type: "uint256" }, + { internalType: "uint256", name: "netPtSwap", type: "uint256" }, + { internalType: "uint256", name: "netYtSwap", type: "uint256" }, + { internalType: "uint256", name: "netSyFromSwap", type: "uint256" }, + { internalType: "uint256", name: "netSyFee", type: "uint256" }, + { internalType: "uint256", name: "totalSyOut", type: "uint256" }, + ], + indexed: false, + internalType: "struct ExitPreExpReturnParams", + name: "params", + type: "tuple", + }, + ], + name: "ExitPreExpToToken", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: true, internalType: "address", name: "YT", type: "address" }, + { indexed: false, internalType: "uint256", name: "netSyIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netPyOut", type: "uint256" }, + ], + name: "MintPyFromSy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "tokenIn", type: "address" }, + { indexed: true, internalType: "address", name: "YT", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netTokenIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netPyOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + name: "MintPyFromToken", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "tokenIn", type: "address" }, + { indexed: true, internalType: "address", name: "SY", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netTokenIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyOut", type: "uint256" }, + ], + name: "MintSyFromToken", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "previousOwner", type: "address" }, + { indexed: true, internalType: "address", name: "newOwner", type: "address" }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: true, internalType: "address", name: "YT", type: "address" }, + { indexed: false, internalType: "uint256", name: "netPyIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyOut", type: "uint256" }, + ], + name: "RedeemPyToSy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "tokenOut", type: "address" }, + { indexed: true, internalType: "address", name: "YT", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netPyIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netTokenOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + name: "RedeemPyToToken", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "tokenOut", type: "address" }, + { indexed: true, internalType: "address", name: "SY", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netSyIn", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netTokenOut", type: "uint256" }, + ], + name: "RedeemSyToToken", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpToRemove", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netPtOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyOut", type: "uint256" }, + ], + name: "RemoveLiquidityDualSyAndPt", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "tokenOut", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpToRemove", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netPtOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netTokenOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + name: "RemoveLiquidityDualTokenAndPt", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpToRemove", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netPtOut", type: "uint256" }, + ], + name: "RemoveLiquiditySinglePt", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpToRemove", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyOut", type: "uint256" }, + ], + name: "RemoveLiquiditySingleSy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "token", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "uint256", name: "netLpToRemove", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netTokenOut", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + name: "RemoveLiquiditySingleToken", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes4", name: "selector", type: "bytes4" }, + { indexed: true, internalType: "address", name: "facet", type: "address" }, + ], + name: "SelectorToFacetSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "int256", name: "netPtToAccount", type: "int256" }, + { indexed: false, internalType: "int256", name: "netSyToAccount", type: "int256" }, + ], + name: "SwapPtAndSy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "token", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "int256", name: "netPtToAccount", type: "int256" }, + { indexed: false, internalType: "int256", name: "netTokenToAccount", type: "int256" }, + { indexed: false, internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + name: "SwapPtAndToken", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "int256", name: "netYtToAccount", type: "int256" }, + { indexed: false, internalType: "int256", name: "netSyToAccount", type: "int256" }, + ], + name: "SwapYtAndSy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "market", type: "address" }, + { indexed: true, internalType: "address", name: "token", type: "address" }, + { indexed: false, internalType: "address", name: "receiver", type: "address" }, + { indexed: false, internalType: "int256", name: "netYtToAccount", type: "int256" }, + { indexed: false, internalType: "int256", name: "netTokenToAccount", type: "int256" }, + { indexed: false, internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + name: "SwapYtAndToken", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "address", name: "market", type: "address" }, + { internalType: "uint256", name: "exactPtIn", type: "uint256" }, + { internalType: "uint256", name: "minSyOut", type: "uint256" }, + { + components: [ + { internalType: "address", name: "limitRouter", type: "address" }, + { internalType: "uint256", name: "epsSkipMarket", type: "uint256" }, + { + components: [ + { + components: [ + { internalType: "uint256", name: "salt", type: "uint256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "enum IPLimitOrderType.OrderType", name: "orderType", type: "uint8" }, + { internalType: "address", name: "token", type: "address" }, + { internalType: "address", name: "YT", type: "address" }, + { internalType: "address", name: "maker", type: "address" }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + { internalType: "uint256", name: "lnImpliedRate", type: "uint256" }, + { internalType: "uint256", name: "failSafeRate", type: "uint256" }, + { internalType: "bytes", name: "permit", type: "bytes" }, + ], + internalType: "struct Order", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + ], + internalType: "struct FillOrderParams[]", + name: "normalFills", + type: "tuple[]", + }, + { + components: [ + { + components: [ + { internalType: "uint256", name: "salt", type: "uint256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "enum IPLimitOrderType.OrderType", name: "orderType", type: "uint8" }, + { internalType: "address", name: "token", type: "address" }, + { internalType: "address", name: "YT", type: "address" }, + { internalType: "address", name: "maker", type: "address" }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + { internalType: "uint256", name: "lnImpliedRate", type: "uint256" }, + { internalType: "uint256", name: "failSafeRate", type: "uint256" }, + { internalType: "bytes", name: "permit", type: "bytes" }, + ], + internalType: "struct Order", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + ], + internalType: "struct FillOrderParams[]", + name: "flashFills", + type: "tuple[]", + }, + { internalType: "bytes", name: "optData", type: "bytes" }, + ], + internalType: "struct LimitOrderData", + name: "limit", + type: "tuple", + }, + ], + name: "swapExactPtForSy", + outputs: [ + { internalType: "uint256", name: "netSyOut", type: "uint256" }, + { internalType: "uint256", name: "netSyFee", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "address", name: "market", type: "address" }, + { internalType: "uint256", name: "exactPtIn", type: "uint256" }, + { + components: [ + { internalType: "address", name: "tokenOut", type: "address" }, + { internalType: "uint256", name: "minTokenOut", type: "uint256" }, + { internalType: "address", name: "tokenRedeemSy", type: "address" }, + { internalType: "address", name: "pendleSwap", type: "address" }, + { + components: [ + { internalType: "enum SwapType", name: "swapType", type: "uint8" }, + { internalType: "address", name: "extRouter", type: "address" }, + { internalType: "bytes", name: "extCalldata", type: "bytes" }, + { internalType: "bool", name: "needScale", type: "bool" }, + ], + internalType: "struct SwapData", + name: "swapData", + type: "tuple", + }, + ], + internalType: "struct TokenOutput", + name: "output", + type: "tuple", + }, + { + components: [ + { internalType: "address", name: "limitRouter", type: "address" }, + { internalType: "uint256", name: "epsSkipMarket", type: "uint256" }, + { + components: [ + { + components: [ + { internalType: "uint256", name: "salt", type: "uint256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "enum IPLimitOrderType.OrderType", name: "orderType", type: "uint8" }, + { internalType: "address", name: "token", type: "address" }, + { internalType: "address", name: "YT", type: "address" }, + { internalType: "address", name: "maker", type: "address" }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + { internalType: "uint256", name: "lnImpliedRate", type: "uint256" }, + { internalType: "uint256", name: "failSafeRate", type: "uint256" }, + { internalType: "bytes", name: "permit", type: "bytes" }, + ], + internalType: "struct Order", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + ], + internalType: "struct FillOrderParams[]", + name: "normalFills", + type: "tuple[]", + }, + { + components: [ + { + components: [ + { internalType: "uint256", name: "salt", type: "uint256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "enum IPLimitOrderType.OrderType", name: "orderType", type: "uint8" }, + { internalType: "address", name: "token", type: "address" }, + { internalType: "address", name: "YT", type: "address" }, + { internalType: "address", name: "maker", type: "address" }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + { internalType: "uint256", name: "lnImpliedRate", type: "uint256" }, + { internalType: "uint256", name: "failSafeRate", type: "uint256" }, + { internalType: "bytes", name: "permit", type: "bytes" }, + ], + internalType: "struct Order", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + ], + internalType: "struct FillOrderParams[]", + name: "flashFills", + type: "tuple[]", + }, + { internalType: "bytes", name: "optData", type: "bytes" }, + ], + internalType: "struct LimitOrderData", + name: "limit", + type: "tuple", + }, + ], + name: "swapExactPtForToken", + outputs: [ + { internalType: "uint256", name: "netTokenOut", type: "uint256" }, + { internalType: "uint256", name: "netSyFee", type: "uint256" }, + { internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "address", name: "market", type: "address" }, + { internalType: "uint256", name: "exactSyIn", type: "uint256" }, + { internalType: "uint256", name: "minPtOut", type: "uint256" }, + { + components: [ + { internalType: "uint256", name: "guessMin", type: "uint256" }, + { internalType: "uint256", name: "guessMax", type: "uint256" }, + { internalType: "uint256", name: "guessOffchain", type: "uint256" }, + { internalType: "uint256", name: "maxIteration", type: "uint256" }, + { internalType: "uint256", name: "eps", type: "uint256" }, + ], + internalType: "struct ApproxParams", + name: "guessPtOut", + type: "tuple", + }, + { + components: [ + { internalType: "address", name: "limitRouter", type: "address" }, + { internalType: "uint256", name: "epsSkipMarket", type: "uint256" }, + { + components: [ + { + components: [ + { internalType: "uint256", name: "salt", type: "uint256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "enum IPLimitOrderType.OrderType", name: "orderType", type: "uint8" }, + { internalType: "address", name: "token", type: "address" }, + { internalType: "address", name: "YT", type: "address" }, + { internalType: "address", name: "maker", type: "address" }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + { internalType: "uint256", name: "lnImpliedRate", type: "uint256" }, + { internalType: "uint256", name: "failSafeRate", type: "uint256" }, + { internalType: "bytes", name: "permit", type: "bytes" }, + ], + internalType: "struct Order", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + ], + internalType: "struct FillOrderParams[]", + name: "normalFills", + type: "tuple[]", + }, + { + components: [ + { + components: [ + { internalType: "uint256", name: "salt", type: "uint256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "enum IPLimitOrderType.OrderType", name: "orderType", type: "uint8" }, + { internalType: "address", name: "token", type: "address" }, + { internalType: "address", name: "YT", type: "address" }, + { internalType: "address", name: "maker", type: "address" }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + { internalType: "uint256", name: "lnImpliedRate", type: "uint256" }, + { internalType: "uint256", name: "failSafeRate", type: "uint256" }, + { internalType: "bytes", name: "permit", type: "bytes" }, + ], + internalType: "struct Order", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + ], + internalType: "struct FillOrderParams[]", + name: "flashFills", + type: "tuple[]", + }, + { internalType: "bytes", name: "optData", type: "bytes" }, + ], + internalType: "struct LimitOrderData", + name: "limit", + type: "tuple", + }, + ], + name: "swapExactSyForPt", + outputs: [ + { internalType: "uint256", name: "netPtOut", type: "uint256" }, + { internalType: "uint256", name: "netSyFee", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "address", name: "market", type: "address" }, + { internalType: "uint256", name: "minPtOut", type: "uint256" }, + { + components: [ + { internalType: "uint256", name: "guessMin", type: "uint256" }, + { internalType: "uint256", name: "guessMax", type: "uint256" }, + { internalType: "uint256", name: "guessOffchain", type: "uint256" }, + { internalType: "uint256", name: "maxIteration", type: "uint256" }, + { internalType: "uint256", name: "eps", type: "uint256" }, + ], + internalType: "struct ApproxParams", + name: "guessPtOut", + type: "tuple", + }, + { + components: [ + { internalType: "address", name: "tokenIn", type: "address" }, + { internalType: "uint256", name: "netTokenIn", type: "uint256" }, + { internalType: "address", name: "tokenMintSy", type: "address" }, + { internalType: "address", name: "pendleSwap", type: "address" }, + { + components: [ + { internalType: "enum SwapType", name: "swapType", type: "uint8" }, + { internalType: "address", name: "extRouter", type: "address" }, + { internalType: "bytes", name: "extCalldata", type: "bytes" }, + { internalType: "bool", name: "needScale", type: "bool" }, + ], + internalType: "struct SwapData", + name: "swapData", + type: "tuple", + }, + ], + internalType: "struct TokenInput", + name: "input", + type: "tuple", + }, + { + components: [ + { internalType: "address", name: "limitRouter", type: "address" }, + { internalType: "uint256", name: "epsSkipMarket", type: "uint256" }, + { + components: [ + { + components: [ + { internalType: "uint256", name: "salt", type: "uint256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "enum IPLimitOrderType.OrderType", name: "orderType", type: "uint8" }, + { internalType: "address", name: "token", type: "address" }, + { internalType: "address", name: "YT", type: "address" }, + { internalType: "address", name: "maker", type: "address" }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + { internalType: "uint256", name: "lnImpliedRate", type: "uint256" }, + { internalType: "uint256", name: "failSafeRate", type: "uint256" }, + { internalType: "bytes", name: "permit", type: "bytes" }, + ], + internalType: "struct Order", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + ], + internalType: "struct FillOrderParams[]", + name: "normalFills", + type: "tuple[]", + }, + { + components: [ + { + components: [ + { internalType: "uint256", name: "salt", type: "uint256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "enum IPLimitOrderType.OrderType", name: "orderType", type: "uint8" }, + { internalType: "address", name: "token", type: "address" }, + { internalType: "address", name: "YT", type: "address" }, + { internalType: "address", name: "maker", type: "address" }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + { internalType: "uint256", name: "lnImpliedRate", type: "uint256" }, + { internalType: "uint256", name: "failSafeRate", type: "uint256" }, + { internalType: "bytes", name: "permit", type: "bytes" }, + ], + internalType: "struct Order", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { internalType: "uint256", name: "makingAmount", type: "uint256" }, + ], + internalType: "struct FillOrderParams[]", + name: "flashFills", + type: "tuple[]", + }, + { internalType: "bytes", name: "optData", type: "bytes" }, + ], + internalType: "struct LimitOrderData", + name: "limit", + type: "tuple", + }, + ], + name: "swapExactTokenForPt", + outputs: [ + { internalType: "uint256", name: "netPtOut", type: "uint256" }, + { internalType: "uint256", name: "netSyFee", type: "uint256" }, + { internalType: "uint256", name: "netSyInterm", type: "uint256" }, + ], + stateMutability: "payable", + type: "function", + }, +] as const; + +export default PendleRouterAbi; diff --git a/abis/PendleStaticRouter.ts b/abis/PendleStaticRouter.ts new file mode 100644 index 0000000..be9d05f --- /dev/null +++ b/abis/PendleStaticRouter.ts @@ -0,0 +1,47 @@ +const PendleStaticRouterAbi = [ + { + inputs: [ + { internalType: "address", name: "market", type: "address" }, + { internalType: "address", name: "tokenIn", type: "address" }, + { internalType: "uint256", name: "amountTokenIn", type: "uint256" }, + { internalType: "uint256", name: "slippage", type: "uint256" }, + ], + outputs: [ + { internalType: "uint256", name: "netPtOut", type: "uint256" }, + { internalType: "uint256", name: "netSyMinted", type: "uint256" }, + { internalType: "uint256", name: "netSyFee", type: "uint256" }, + { internalType: "uint256", name: "priceImpact", type: "uint256" }, + { internalType: "uint256", name: "exchangeRateAfter", type: "uint256" }, + { + components: [ + { internalType: "uint256", name: "guessMin", type: "uint256" }, + { internalType: "uint256", name: "guessMax", type: "uint256" }, + { internalType: "uint256", name: "guessOffchain", type: "uint256" }, + { internalType: "uint256", name: "maxIteration", type: "uint256" }, + { internalType: "uint256", name: "eps", type: "uint256" }, + ], + internalType: "struct ApproxParams", + name: "guessPtOut", + type: "tuple", + }, + ], + stateMutability: "view", + name: "swapExactTokenForPtStaticAndGenerateApproxParams", + type: "function", + }, + { + type: "function", + stateMutability: "view", + name: "getYieldTokenAndPtRate", + inputs: [ + { internalType: "address", name: "market", type: "address" }, + ], + outputs: [ + { internalType: "address", name: "yieldToken", type: "address" }, + { internalType: "uint256", name: "netPtOut", type: "uint256" }, + { internalType: "uint256", name: "netYieldTokenOut", type: "uint256" }, + ], + } +] as const; + +export default PendleStaticRouterAbi; diff --git a/abis/PendleSy.ts b/abis/PendleSy.ts new file mode 100644 index 0000000..76f9578 --- /dev/null +++ b/abis/PendleSy.ts @@ -0,0 +1,406 @@ +const PendleSyAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { + inputs: [ + { internalType: "uint256", name: "actualSharesOut", type: "uint256" }, + { internalType: "uint256", name: "requiredSharesOut", type: "uint256" }, + ], + name: "SYInsufficientSharesOut", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "actualTokenOut", type: "uint256" }, + { internalType: "uint256", name: "requiredTokenOut", type: "uint256" }, + ], + name: "SYInsufficientTokenOut", + type: "error", + }, + { inputs: [{ internalType: "address", name: "token", type: "address" }], name: "SYInvalidTokenIn", type: "error" }, + { inputs: [{ internalType: "address", name: "token", type: "address" }], name: "SYInvalidTokenOut", type: "error" }, + { inputs: [], name: "SYZeroDeposit", type: "error" }, + { inputs: [], name: "SYZeroRedeem", type: "error" }, + { + 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: "user", type: "address" }, + { indexed: false, internalType: "address[]", name: "rewardTokens", type: "address[]" }, + { indexed: false, internalType: "uint256[]", name: "rewardAmounts", type: "uint256[]" }, + ], + name: "ClaimRewards", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: true, internalType: "address", name: "tokenIn", type: "address" }, + { indexed: false, internalType: "uint256", name: "amountDeposited", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "amountSyOut", type: "uint256" }, + ], + name: "Deposit", + type: "event", + }, + { anonymous: false, inputs: [], name: "EIP712DomainChanged", type: "event" }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: "uint8", name: "version", type: "uint8" }], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "previousOwner", type: "address" }, + { indexed: true, internalType: "address", name: "newOwner", type: "address" }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: "address", name: "account", type: "address" }], + name: "Paused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "caller", type: "address" }, + { indexed: true, internalType: "address", name: "receiver", type: "address" }, + { indexed: true, internalType: "address", name: "tokenOut", type: "address" }, + { indexed: false, internalType: "uint256", name: "amountSyToRedeem", type: "uint256" }, + { indexed: false, internalType: "uint256", name: "amountTokenOut", type: "uint256" }, + ], + name: "Redeem", + 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", + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: "address", name: "account", type: "address" }], + name: "Unpaused", + type: "event", + }, + { + inputs: [], + name: "DOMAIN_SEPARATOR", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "accruedRewards", + outputs: [{ internalType: "uint256[]", name: "rewardAmounts", type: "uint256[]" }], + 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: [], + name: "assetInfo", + outputs: [ + { internalType: "enum IStandardizedYield.AssetType", name: "assetType", type: "uint8" }, + { internalType: "address", name: "assetAddress", type: "address" }, + { internalType: "uint8", name: "assetDecimals", type: "uint8" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { inputs: [], name: "claimOwnership", outputs: [], stateMutability: "nonpayable", type: "function" }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "claimRewards", + outputs: [{ internalType: "uint256[]", name: "rewardAmounts", type: "uint256[]" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "address", name: "tokenIn", type: "address" }, + { internalType: "uint256", name: "amountTokenToDeposit", type: "uint256" }, + { internalType: "uint256", name: "minSharesOut", type: "uint256" }, + ], + name: "deposit", + outputs: [{ internalType: "uint256", name: "amountSharesOut", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "eip712Domain", + outputs: [ + { internalType: "bytes1", name: "fields", type: "bytes1" }, + { internalType: "string", name: "name", type: "string" }, + { internalType: "string", name: "version", type: "string" }, + { internalType: "uint256", name: "chainId", type: "uint256" }, + { internalType: "address", name: "verifyingContract", type: "address" }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "uint256[]", name: "extensions", type: "uint256[]" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "exchangeRate", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getRewardTokens", + outputs: [{ internalType: "address[]", name: "rewardTokens", type: "address[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTokensIn", + outputs: [{ internalType: "address[]", name: "res", type: "address[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTokensOut", + outputs: [{ internalType: "address[]", name: "res", type: "address[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "_name", type: "string" }, + { internalType: "string", name: "_symbol", type: "string" }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "token", type: "address" }], + name: "isValidTokenIn", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "token", type: "address" }], + name: "isValidTokenOut", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + 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: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { inputs: [], name: "pause", outputs: [], stateMutability: "nonpayable", type: "function" }, + { + inputs: [], + name: "paused", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pendingOwner", + outputs: [{ internalType: "address", name: "", type: "address" }], + 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: [ + { internalType: "address", name: "tokenIn", type: "address" }, + { internalType: "uint256", name: "amountTokenToDeposit", type: "uint256" }, + ], + name: "previewDeposit", + outputs: [{ internalType: "uint256", name: "amountSharesOut", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "tokenOut", type: "address" }, + { internalType: "uint256", name: "amountSharesToRedeem", type: "uint256" }, + ], + name: "previewRedeem", + outputs: [{ internalType: "uint256", name: "amountTokenOut", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "amountSharesToRedeem", type: "uint256" }, + { internalType: "address", name: "tokenOut", type: "address" }, + { internalType: "uint256", name: "minTokenOut", type: "uint256" }, + { internalType: "bool", name: "burnFromInternalBalance", type: "bool" }, + ], + name: "redeem", + outputs: [{ internalType: "uint256", name: "amountTokenOut", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "rewardIndexesCurrent", + outputs: [{ internalType: "uint256[]", name: "indexes", type: "uint256[]" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "rewardIndexesStored", + outputs: [{ internalType: "uint256[]", name: "indexes", type: "uint256[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "newOwner", type: "address" }, + { internalType: "bool", name: "direct", type: "bool" }, + { internalType: "bool", name: "renounce", type: "bool" }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { inputs: [], name: "unpause", outputs: [], stateMutability: "nonpayable", type: "function" }, + { + inputs: [], + name: "yieldToken", + outputs: [{ internalType: "address", name: "yieldToken", type: "address" }], + stateMutability: "view", + type: "function", + }, + { stateMutability: "payable", type: "receive" }, +]; + +export default PendleSyAbi; \ No newline at end of file diff --git a/abis/PreLiquidationRebalancer.ts b/abis/PreLiquidationRebalancer.ts index e62f1c5..def16cd 100644 --- a/abis/PreLiquidationRebalancer.ts +++ b/abis/PreLiquidationRebalancer.ts @@ -1,4 +1,4 @@ -export const PreLiquidationRebalancerAbi = [ +const PreLiquidationRebalancerBaseAbi = [ { "type": "constructor", "inputs": [ @@ -284,3 +284,24 @@ export const PreLiquidationRebalancerAbi = [ "inputs": [] } ] as const; + +export const PreLiquidationRebalancerAbi = [ + ...PreLiquidationRebalancerBaseAbi, + { + "type": "error", + "name": "AuctionNotValid", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidLeverageTokenStateAfterRebalance", + "inputs": [ + { + "name": "leverageToken", + "type": "address", + "internalType": "address" + }, + ] + } +] as const; + diff --git a/src/constants/contracts.ts b/src/constants/contracts.ts index 8a608da..61c6c20 100644 --- a/src/constants/contracts.ts +++ b/src/constants/contracts.ts @@ -15,6 +15,8 @@ export const CONTRACT_ADDRESSES: Record = { FLUID_DEX_RESERVES_RESOLVER: "0xC93876C0EEd99645DD53937b25433e311881A27C", LEVERAGE_MANAGER: "0x5C37EB148D4a261ACD101e2B997A0F163Fb3E351", MULTICALL_EXECUTOR: "0x16D02Ebd89988cAd1Ce945807b963aB7A9Fd22E1", + PENDLE_ROUTER: "0x888888888889758F76e7103c6CbF23ABbF58F946", + PENDLE_ROUTER_STATIC: "0x263833d47eA3fA4a30f269323aba6a107f9eB14C", PRE_LIQUIDATION_REBALANCER: process.env.PRE_LIQUIDATION_REBALANCER_ADDRESS as Address || zeroAddress, WSTETH: "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", UNISWAP_SWAP_ROUTER_02: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", diff --git a/src/constants/values.ts b/src/constants/values.ts index aae3acf..e8ba1c1 100644 --- a/src/constants/values.ts +++ b/src/constants/values.ts @@ -42,6 +42,7 @@ export const ETHERFI_L2_MODE_SYNC_POOL_ETH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEE export const LIFI_API_KEY = process.env.LIFI_API_KEY || null; export const BALMY_SLIPPAGE_PERCENTAGE = Number(process.env.BALMY_SLIPPAGE_PERCENTAGE) || 1; // 1% by default +export const DEX_SLIPPAGE_BPS = BigInt(String(process.env.DEX_SLIPPAGE_BPS || "10")); // 0.1% by default export const CHECK_PROFITABILITY_WITH_GAS_FEE = Boolean(process.env.CHECK_PROFITABILITY_WITH_GAS_FEE && process.env.CHECK_PROFITABILITY_WITH_GAS_FEE.toLowerCase() === "true"); diff --git a/src/services/preLiquidationRebalance.ts b/src/services/preLiquidationRebalance.ts index 942e5f6..c9b7c4e 100644 --- a/src/services/preLiquidationRebalance.ts +++ b/src/services/preLiquidationRebalance.ts @@ -133,10 +133,13 @@ const executePreLiquidationRebalance = async ( const swapParams = await getRebalanceSwapParams({ leverageToken, stakeType: StakeType.NONE, + receiver: CONTRACT_ADDRESSES[CHAIN_ID].PRE_LIQUIDATION_REBALANCER, assetIn, assetOut, takeAmount, requiredAmountIn, + collateralAsset, + debtAsset, }); if (!swapParams.isProfitable) { diff --git a/src/services/routing/balmy.ts b/src/services/routing/balmy.ts index 8cec53c..4c9e856 100644 --- a/src/services/routing/balmy.ts +++ b/src/services/routing/balmy.ts @@ -1,11 +1,10 @@ import { ComponentLogger } from "../../utils/logger"; -import { erc20Abi, zeroAddress } from "viem"; +import { encodeFunctionData, erc20Abi, zeroAddress } from "viem"; import { buildSDK, QuoteRequest, QuoteResponse } from "@balmy/sdk"; import { Call } from "../../types"; -import { encodeFunctionData } from "viem"; import { publicClient } from "../../utils/transactionHelpers"; import { ALCHEMY_API_KEY } from "../../constants/chain"; -import { LIFI_API_KEY } from "@/constants/values"; +import { LIFI_API_KEY } from "../../constants/values"; const sdk = buildSDK({ quotes: { diff --git a/src/services/routing/fluid.ts b/src/services/routing/fluid.ts index f310a92..2ad7354 100644 --- a/src/services/routing/fluid.ts +++ b/src/services/routing/fluid.ts @@ -1,4 +1,4 @@ -import { Address, encodeFunctionData, erc20Abi, getAddress } from "viem"; +import { Address, encodeFunctionData, erc20Abi, getAddress, isAddressEqual, zeroAddress } from "viem"; import FluidDexReservesResolverAbi from "../../../abis/FluidDexReservesResolver"; import { CONTRACT_ADDRESSES } from "../../constants/contracts"; import { CHAIN_ID } from "../../constants/chain"; @@ -6,6 +6,7 @@ import { publicClient } from "../../utils/transactionHelpers"; import FluidDexT1Abi from "../../../abis/FluidDexT1"; import { Call } from "../../types"; import { ComponentLogger } from "../../utils/logger"; +import { getDexSlippageAdjustedAmount } from "../../utils/math"; export class FluidDex { // Maps from token pair `tokenA-tokenB` to an array of pool addresses that serve the pair @@ -19,15 +20,12 @@ export class FluidDex { toToken: Address, fromAmount: bigint, logger: ComponentLogger - ): Promise<{ amountOut: bigint; pool: Address | null }> { + ): Promise<{ amountOut: bigint; minAmountOut: bigint; pool: Address } | null> { try { const pools = await this.getPools(fromToken, toToken); if (!pools || pools.length === 0) { - return { - amountOut: 0n, - pool: null, - }; + return null; } const estimates = await publicClient.multicall({ @@ -35,7 +33,7 @@ export class FluidDex { address: CONTRACT_ADDRESSES[CHAIN_ID].FLUID_DEX_RESERVES_RESOLVER as Address, abi: FluidDexReservesResolverAbi, functionName: "estimateSwapIn", - args: [pool, this.poolsToTokenPairCache.get(pool)?.[0] === fromToken, fromAmount, 0n], + args: [pool, isAddressEqual(this.poolsToTokenPairCache.get(pool)?.[0] || zeroAddress, fromToken), fromAmount, 0n], })), }); @@ -52,25 +50,25 @@ export class FluidDex { } }); + const bestEstimateWithSlippage = getDexSlippageAdjustedAmount(bestEstimate); + return { amountOut: bestEstimate, - pool: bestPool, + minAmountOut: bestEstimateWithSlippage, + pool: bestPool!, }; } catch (error) { logger.dexQuoteError({ error }, 'Error estimating swap in with Fluid DEX'); - return { - amountOut: 0n, - pool: null, - }; + return null; } } - prepareSwapCalldata(pool: Address | null, fromToken: Address, fromAmount: bigint): Call[] { + prepareSwapCalldata(receiver: Address, pool: Address | null, fromToken: Address, fromAmount: bigint): Call[] { if (!pool) { return []; } - const swap0to1 = this.poolsToTokenPairCache.get(pool)?.[0] === fromToken; + const swap0to1 = isAddressEqual(this.poolsToTokenPairCache.get(pool)?.[0] || zeroAddress, fromToken); const approveCalldata = encodeFunctionData({ abi: erc20Abi, @@ -89,7 +87,7 @@ export class FluidDex { data: encodeFunctionData({ abi: FluidDexT1Abi, functionName: "swapIn", - args: [swap0to1, fromAmount, 0n, CONTRACT_ADDRESSES[CHAIN_ID].DUTCH_AUCTION_REBALANCER], // Recipient of the swap is the rebalancer contract + args: [swap0to1, fromAmount, 0n, receiver], }), value: 0n, }, diff --git a/src/services/routing/getSwapParams.ts b/src/services/routing/getSwapParams.ts index 7202104..0fa7419 100644 --- a/src/services/routing/getSwapParams.ts +++ b/src/services/routing/getSwapParams.ts @@ -1,7 +1,9 @@ +import { getAddress } from "viem"; import {getEtherFiEthStakeQuote, prepareEtherFiEthStakeCalldata} from "./etherFi"; import { getAmountsOutUniswapV2, prepareUniswapV2SwapCalldata } from "./uniswapV2"; import { getRouteUniswapV3ExactInput, prepareUniswapV3SwapCalldata } from "./uniswapV3"; import { + GetDexSwapParamsOutput, GetRebalanceSwapParamsInput, GetRebalanceSwapParamsOutput, StakeType, @@ -10,9 +12,9 @@ import { getLidoEthStakeQuote, prepareLidoEthStakeCalldata } from "./lido"; import { BALMY_SLIPPAGE_PERCENTAGE } from "../../constants/values"; import { FLUID_DEX } from "./fluid"; import { createComponentLogger } from "../../utils/logger"; +import { getPendleSwapQuote } from "./pendle"; import { getBalmyQuote, prepareBalmySwapCalldata } from "./balmy"; import { CHAIN_ID } from "../../constants/chain"; -import { getAddress } from "viem"; import { CONTRACT_ADDRESSES } from "../../constants/contracts"; const logger = createComponentLogger('getRebalanceSwapParams'); @@ -50,12 +52,11 @@ export const getRebalanceSwapParams = async ( export const getDexSwapParams = async ( input: GetRebalanceSwapParamsInput, - requiredAmountIn: bigint -): Promise => { - const { leverageToken, assetIn, assetOut, takeAmount } = input; + requiredAmountIn: bigint, +): Promise => { + const { leverageToken, receiver, assetIn, assetOut, takeAmount, collateralAsset, debtAsset } = input; - // Fetch routes and quotes from DEXes directly - const [balmyQuote, amountOutUniV2, uniswapV3Route, fluidDexRoute] = await Promise.all([ + const [balmyQuote, uniswapV2Quote, uniswapV3Route, fluidDexRoute, pendleQuote] = await Promise.all([ getBalmyQuote({ chainId: CHAIN_ID, sellToken: assetOut as `0x${string}`, @@ -66,7 +67,7 @@ export const getDexSwapParams = async ( }, slippagePercentage: BALMY_SLIPPAGE_PERCENTAGE, takerAddress: getAddress(CONTRACT_ADDRESSES[CHAIN_ID].MULTICALL_EXECUTOR), - recipient: getAddress(CONTRACT_ADDRESSES[CHAIN_ID].DUTCH_AUCTION_REBALANCER), + recipient: getAddress(receiver), }, logger), getAmountsOutUniswapV2({ inputTokenAddress: assetOut, @@ -74,23 +75,34 @@ export const getDexSwapParams = async ( amountInRaw: takeAmount.toString(), }, logger), getRouteUniswapV3ExactInput({ + receiver, tokenInAddress: assetOut, tokenOutAddress: assetIn, amountInRaw: takeAmount.toString(), }, logger), FLUID_DEX.getEstimateSwapIn(assetOut, assetIn, takeAmount, logger), + getPendleSwapQuote({ + leverageToken, + receiver, + collateralAsset, + debtAsset, + fromAsset: assetOut, + toAsset: assetIn, + fromAmount: takeAmount, + }, logger), ]); - if (!balmyQuote && !amountOutUniV2 && !uniswapV3Route && !fluidDexRoute) { + if (!balmyQuote && !uniswapV2Quote && !uniswapV3Route && !fluidDexRoute && !pendleQuote) { throw new Error('No quotes found'); } // Find the best route by comparing all three options const routes = [ - { amountOut: balmyQuote?.buyAmount.amount || 0n, prepareCalldata: async () => await prepareBalmySwapCalldata(balmyQuote!, logger) }, - { amountOut: amountOutUniV2, prepareCalldata: async () => prepareUniswapV2SwapCalldata(assetOut, assetIn, takeAmount, requiredAmountIn) }, - { amountOut: BigInt((uniswapV3Route?.rawQuote || "0").toString()), prepareCalldata: async () => prepareUniswapV3SwapCalldata(assetOut, uniswapV3Route!, takeAmount, requiredAmountIn) }, - { amountOut: fluidDexRoute.amountOut, prepareCalldata: async () => FLUID_DEX.prepareSwapCalldata(fluidDexRoute.pool, assetOut, takeAmount) } + { amountOut: balmyQuote?.buyAmount.amount || 0n, minAmountOut: balmyQuote?.minBuyAmount.amount || 0n, prepareCalldata: async () => prepareBalmySwapCalldata(balmyQuote!, logger) }, + { amountOut: uniswapV2Quote?.amountOut || 0n, minAmountOut: uniswapV2Quote?.minAmountOut || 0n, prepareCalldata: async () => prepareUniswapV2SwapCalldata(receiver, assetOut, assetIn, takeAmount, requiredAmountIn) }, + { amountOut: BigInt((uniswapV3Route?.route.rawQuote || "0").toString()), minAmountOut: uniswapV3Route?.minAmountOut || 0n, prepareCalldata: async () => prepareUniswapV3SwapCalldata(receiver, assetOut, uniswapV3Route!.route, takeAmount, requiredAmountIn) }, + { amountOut: fluidDexRoute?.amountOut || 0n, minAmountOut: fluidDexRoute?.minAmountOut || 0n, prepareCalldata: async () => FLUID_DEX.prepareSwapCalldata(receiver, fluidDexRoute!.pool, assetOut, takeAmount) }, + { amountOut: pendleQuote?.amountOut || 0n, minAmountOut: pendleQuote?.minAmountOut || 0n, prepareCalldata: async () => pendleQuote ? pendleQuote.prepareCalldata(pendleQuote) : [] }, ]; logger.debug({ @@ -98,10 +110,12 @@ export const getDexSwapParams = async ( assetIn, assetOut, fromAmount: takeAmount, + requiredAmountOut: requiredAmountIn, balmy: routes[0].amountOut, uniswapV2: routes[1].amountOut, uniswapV3: routes[2].amountOut, fluid: routes[3].amountOut, + pendle: routes[4].amountOut, }, 'DEX swap quotes'); // Sort by amountOut in descending order and pick the best one @@ -113,6 +127,7 @@ export const getDexSwapParams = async ( return { isProfitable: false, amountOut: bestRoute.amountOut, + minAmountOut: bestRoute.minAmountOut, swapCalls: [], } } @@ -120,6 +135,7 @@ export const getDexSwapParams = async ( return { isProfitable: true, amountOut: bestRoute.amountOut, + minAmountOut: bestRoute.minAmountOut, swapCalls: await bestRoute.prepareCalldata() || [], }; }; \ No newline at end of file diff --git a/src/services/routing/pendle.ts b/src/services/routing/pendle.ts new file mode 100644 index 0000000..c1a1e7f --- /dev/null +++ b/src/services/routing/pendle.ts @@ -0,0 +1,336 @@ +import { ComponentLogger } from "../../utils/logger"; +import { CHAIN_ID } from "../../constants/chain"; +import { Address, encodeFunctionData, erc20Abi, isAddressEqual, getAddress, maxUint256, zeroAddress } from "viem"; +import { CONTRACT_ADDRESSES } from "../../constants/contracts"; +import { Call, GetPendleSwapQuoteInput, GetPendleSwapQuoteOutput, StakeType, GetDexSwapParamsOutput } from "../../types"; +import { getPendleStaticRouterContract } from "../../utils/contractHelpers"; +import { getDexSwapParams } from "./getSwapParams"; +import PendleRouterAbi from "../../../abis/PendleRouter"; +import PendleMarketV3Abi from "../../../abis/PendleMarketV3"; +import PendleSyAbi from "../../../abis/PendleSy"; +import { getTokenDecimals } from "../../utils/tokens"; +import { publicClient } from "../../utils/transactionHelpers"; +import { getDexSlippageAdjustedAmount } from "../../utils/math"; +import { DEX_SLIPPAGE_BPS } from "../../constants/values"; + +// Maps from PT address to the Pendle market address to query for quotes +const PT_TO_PENDLE_MARKET = new Map([ + [ + // PT-RLP-4DEC2025 + getAddress("0x3A70F0C696dcB3A4aB3833cD9726397dD61AC85e"), + "0x9942a74e6E75cEa2DB5D068c1E75C9ac687bcA06", + ], +]); + +const PENDLE_MARKET_TO_YIELD_TOKEN = new Map(); + +export const getPendleSwapQuote = async ( + args: GetPendleSwapQuoteInput, + logger: ComponentLogger +): Promise => { + if (PT_TO_PENDLE_MARKET.has(getAddress(args.fromAsset))) { + return getPendleSwapExactPtForTokenQuote(args, logger); + } + if (PT_TO_PENDLE_MARKET.has(getAddress(args.toAsset))) { + return getPendleSwapExactTokenForPtQuote(args, logger); + } + + return null; +}; + +const getPendleSwapExactPtForTokenQuote = async ( + args: GetPendleSwapQuoteInput, + logger: ComponentLogger +): Promise => { + const { leverageToken, receiver, collateralAsset, debtAsset, fromAsset, toAsset, fromAmount } = args; + + const pt = getAddress(fromAsset); + + try { + const pendleRouterAddress = CONTRACT_ADDRESSES[CHAIN_ID].PENDLE_ROUTER; + if (!pendleRouterAddress) { + return null; + } + + const market = PT_TO_PENDLE_MARKET.get(pt); + if (!market) { + logger.dexQuoteError({ pt }, "No Pendle market found for PT"); + return null; + } + + const staticRouter = getPendleStaticRouterContract(); + const [yieldToken, , yieldTokenRate] = await staticRouter.read.getYieldTokenAndPtRate([market]); + + if (yieldTokenRate === maxUint256) { + logger.dexQuoteError({ pt, yieldToken, market, yieldTokenRate }, "Yield token rate is max uint256 for PT -> yield token swap, swap cannot be executed"); + return null; + } + + PENDLE_MARKET_TO_YIELD_TOKEN.set(market, yieldToken); + + const isToTokenYieldToken = isAddressEqual(toAsset, yieldToken); + + const ptDecimals = (await getTokenDecimals([pt]))[pt]; + // We apply a default slippage to the estimate to avoid the risk of on-chain execution reverts due to state change + // between the simulation and the actual execution. + const yieldTokenAmountOutWithSlippage = getDexSlippageAdjustedAmount((yieldTokenRate * fromAmount) / 10n ** BigInt(ptDecimals)); + + const tokenOutput = createTokenOutputSimple(yieldToken, yieldTokenAmountOutWithSlippage); + const emptyLimit = createEmptyLimitOrderData(); + const calldata = encodeFunctionData({ + abi: PendleRouterAbi, + functionName: "swapExactPtForToken", + args: [ + isToTokenYieldToken ? receiver : CONTRACT_ADDRESSES[CHAIN_ID].MULTICALL_EXECUTOR, + market, + fromAmount, + tokenOutput, + emptyLimit, + ], + }); + + // Swap calldata for underlying yield token of PT -> toToken + let underlyingSwapData: GetDexSwapParamsOutput | null = null; + if (!isToTokenYieldToken) { + underlyingSwapData = await getDexSwapParams( + { + leverageToken, + receiver, + stakeType: StakeType.NONE, + assetIn: toAsset, + assetOut: yieldToken, + takeAmount: yieldTokenAmountOutWithSlippage, + requiredAmountIn: 0n, + collateralAsset, + debtAsset, + }, + 0n + ); + } + + return { + amountOut: underlyingSwapData?.amountOut || 0n, + minAmountOut: underlyingSwapData?.minAmountOut || 0n, + pendleSwapData: { + amountIn: fromAmount, + assetIn: pt, + data: calldata, + to: pendleRouterAddress, + value: 0n, + }, + underlyingSwapData, + to: pendleRouterAddress, + value: 0n, + prepareCalldata: (quote: GetPendleSwapQuoteOutput) => preparePendleSwapExactPtForTokenCalldata(quote), + }; + } catch (error) { + logger.dexQuoteError({ error }, "Error getting Pendle swap quote"); + return null; + } +}; + +const getPendleSwapExactTokenForPtQuote = async ( + args: GetPendleSwapQuoteInput, + logger: ComponentLogger +): Promise => { + const { leverageToken, receiver, collateralAsset, debtAsset, fromAsset, toAsset, fromAmount } = args; + + const pt = getAddress(toAsset); + + try { + const pendleRouterAddress = CONTRACT_ADDRESSES[CHAIN_ID].PENDLE_ROUTER; + if (!pendleRouterAddress) { + logger.dexQuoteError({ pendleRouterAddress }, "Pendle router address is not set"); + return null; + } + + const market = PT_TO_PENDLE_MARKET.get(pt); + if (!market) { + logger.dexQuoteError({ pt }, "No Pendle market found for PT"); + return null; + } + + let yieldToken = PENDLE_MARKET_TO_YIELD_TOKEN.get(getAddress(market)); + if (!yieldToken) { + try { + const [syToken,,] = await publicClient.readContract({ + address: market, + abi: PendleMarketV3Abi, + functionName: "readTokens", + }); + yieldToken = (await publicClient.readContract({ + address: syToken, + abi: PendleSyAbi, + functionName: "yieldToken", + })) as Address; + PENDLE_MARKET_TO_YIELD_TOKEN.set(market, yieldToken); + } catch (error) { + logger.error({ error, pt, market }, "Error getting Pendle yield token"); + throw error; + } + } + + const isFromTokenYieldToken = isAddressEqual(getAddress(fromAsset), getAddress(yieldToken)); + + // Swap calldata for from token -> underlying token of PT + let underlyingSwapData: GetDexSwapParamsOutput | null = null; + let underlyingAmountInForPt = fromAmount; + if (!isFromTokenYieldToken) { + underlyingSwapData = await getDexSwapParams( + { + leverageToken, + // Set to the multicall executor because after the swap, the underlying token of the PT is used to mint PTs + receiver: CONTRACT_ADDRESSES[CHAIN_ID].MULTICALL_EXECUTOR, + stakeType: StakeType.NONE, + assetIn: yieldToken, + assetOut: fromAsset, + takeAmount: fromAmount, + requiredAmountIn: 0n, + collateralAsset, + debtAsset, + }, + 0n, + ); + + // We use the min amount out for the intermediate swap to avoid reverts + underlyingAmountInForPt = underlyingSwapData.minAmountOut; + } + + const staticRouter = getPendleStaticRouterContract(); + const pendleSlippage = DEX_SLIPPAGE_BPS * 100000000000000n; + const result = await staticRouter.read.swapExactTokenForPtStaticAndGenerateApproxParams([ + market, + yieldToken, + underlyingAmountInForPt, + pendleSlippage, + ]); + const guessPtOut = result[5]; + + const approxParams = guessPtOut; + const tokenInput = createTokenInputSimple(yieldToken, underlyingAmountInForPt); + const emptyLimit = createEmptyLimitOrderData(); + const calldata = encodeFunctionData({ + abi: PendleRouterAbi, + functionName: "swapExactTokenForPt", + args: [ + receiver, + market, + 0n, + approxParams, + tokenInput, + emptyLimit, + ], + }); + + return { + amountOut: guessPtOut.guessOffchain, + minAmountOut: guessPtOut.guessMin, + pendleSwapData: { + amountIn: underlyingAmountInForPt, + assetIn: yieldToken, + data: calldata, + to: pendleRouterAddress, + value: 0n, + }, + underlyingSwapData, + to: pendleRouterAddress, + value: 0n, + prepareCalldata: (quote: GetPendleSwapQuoteOutput) => preparePendleSwapExactTokenForPtCalldata(quote), + }; + } catch (error) { + logger.dexQuoteError({ error }, "Error getting Pendle swap quote"); + return null; + } +}; + +export const preparePendleSwapExactPtForTokenCalldata = (pendleQuote: { + pendleSwapData: { amountIn: bigint; assetIn: Address; data: `0x${string}`; to: Address; value: bigint }; + underlyingSwapData: GetDexSwapParamsOutput | null; +}): Call[] => { + const pendleRouterApproveCalldata = encodeFunctionData({ + abi: erc20Abi, + functionName: "approve", + args: [pendleQuote.pendleSwapData.to, pendleQuote.pendleSwapData.amountIn], + }); + + return [ + { + target: pendleQuote.pendleSwapData.assetIn, + data: pendleRouterApproveCalldata, + value: 0n, + }, + { + target: pendleQuote.pendleSwapData.to, + data: pendleQuote.pendleSwapData.data, + value: 0n, + }, + ...(pendleQuote.underlyingSwapData?.swapCalls || []), + ]; +}; + +export const preparePendleSwapExactTokenForPtCalldata = (pendleQuote: { + pendleSwapData: { amountIn: bigint; assetIn: Address; data: `0x${string}`; to: Address; value: bigint }; + underlyingSwapData: GetDexSwapParamsOutput | null; +}): Call[] => { + const pendleRouterApproveCalldata = encodeFunctionData({ + abi: erc20Abi, + functionName: "approve", + args: [pendleQuote.pendleSwapData.to, pendleQuote.pendleSwapData.amountIn], + }); + + const calldata = [ + ...(pendleQuote.underlyingSwapData?.swapCalls || []), + { + target: pendleQuote.pendleSwapData.assetIn, + data: pendleRouterApproveCalldata, + value: 0n, + }, + { + target: pendleQuote.pendleSwapData.to, + data: pendleQuote.pendleSwapData.data, + value: 0n, + }, + ]; + + return calldata; +}; + +const createTokenInputSimple = (tokenIn: Address, netTokenIn: bigint) => { + return { + tokenIn, + netTokenIn, + tokenMintSy: tokenIn, + pendleSwap: zeroAddress, + swapData: { + swapType: 0, + extRouter: zeroAddress, + extCalldata: "" as `0x${string}`, + needScale: false, + }, + }; +}; + +const createTokenOutputSimple = (tokenOut: Address, minTokenOut: bigint) => { + return { + tokenOut, + minTokenOut, + tokenRedeemSy: tokenOut, + pendleSwap: zeroAddress, + swapData: { + swapType: 0, + extRouter: zeroAddress, + extCalldata: "" as `0x${string}`, + needScale: false, + }, + }; +}; + +const createEmptyLimitOrderData = () => { + return { + limitRouter: zeroAddress, + epsSkipMarket: 0n, + normalFills: [], + flashFills: [], + optData: "" as `0x${string}`, + }; +}; diff --git a/src/services/routing/uniswapV2.ts b/src/services/routing/uniswapV2.ts index fb774d1..77c1166 100644 --- a/src/services/routing/uniswapV2.ts +++ b/src/services/routing/uniswapV2.ts @@ -5,15 +5,16 @@ import UniswapV2Router02Abi from "../../../abis/UniswapV2Router02"; import { CONTRACT_ADDRESSES } from "../../constants/contracts"; import { CHAIN_ID } from "../../constants/chain"; import { ComponentLogger } from "../../utils/logger"; +import { getDexSlippageAdjustedAmount } from "../../utils/math"; -export const getAmountsOutUniswapV2 = async (args: UniswapV2GetAmountsOutArgs, logger: ComponentLogger) => { +export const getAmountsOutUniswapV2 = async (args: UniswapV2GetAmountsOutArgs, logger: ComponentLogger): Promise<{ amountOut: bigint; minAmountOut: bigint } | null> => { try { const router = getUniswapV2Router02Contract(); const { inputTokenAddress, outputTokenAddress, amountInRaw } = args; if (amountInRaw === "0") { - return 0n; + return null; } // Convert the input string to BigInt (this is the raw base-units value) @@ -28,15 +29,17 @@ export const getAmountsOutUniswapV2 = async (args: UniswapV2GetAmountsOutArgs, l // For a 2-token path, amountsOut = [amountIn, amountOut] const outputAmountRaw = amountsOut[1]; - return outputAmountRaw; + return { + amountOut: outputAmountRaw, + minAmountOut: getDexSlippageAdjustedAmount(outputAmountRaw), + } } catch (error) { logger.dexQuoteError({ error }, 'Error calling Uniswap V2 Router02 getAmountsOut'); - return 0n; + return null; } }; - -export const prepareUniswapV2SwapCalldata = (assetIn: Address, assetOut: Address, inputAmount: bigint, outputAmountMin: bigint): Call[] => { +export const prepareUniswapV2SwapCalldata = (receiver: Address, assetIn: Address, assetOut: Address, inputAmount: bigint, outputAmountMin: bigint): Call[] => { const uniswapV2RouterAbi = UniswapV2Router02Abi; // Approve the router to spend the input amount @@ -53,7 +56,7 @@ export const prepareUniswapV2SwapCalldata = (assetIn: Address, assetOut: Address inputAmount, outputAmountMin, [assetIn, assetOut], - CONTRACT_ADDRESSES[CHAIN_ID].DUTCH_AUCTION_REBALANCER, // Recipient of the swap is the rebalancer contract + receiver, BigInt(Math.floor(Date.now() / 1000) + 60) // Deadline is set to 60 seconds from now ], }); diff --git a/src/services/routing/uniswapV3.ts b/src/services/routing/uniswapV3.ts index 387d19d..bc510ad 100644 --- a/src/services/routing/uniswapV3.ts +++ b/src/services/routing/uniswapV3.ts @@ -5,11 +5,12 @@ import { CurrencyAmount, Percent, Token, TradeType } from "@uniswap/sdk-core"; import { encodeRouteToPath } from "@uniswap/v3-sdk"; import { CONTRACT_ADDRESSES } from "../../constants/contracts"; import { Call, UniswapV3QuoteExactInputArgs } from "../../types"; -import { IS_USING_FORK } from "../../constants/values"; +import { DEX_SLIPPAGE_BPS, IS_USING_FORK } from "../../constants/values"; import { primaryEthersProvider, publicClient } from "../../utils/transactionHelpers"; import UniswapSwapRouter02Abi from "../../../abis/UniswapSwapRouter02"; import { CHAIN_ID } from "../../constants/chain"; import { ComponentLogger } from "../../utils/logger"; +import { getDexSlippageAdjustedAmount } from "../../utils/math"; class StaticGasPriceProvider implements IGasPriceProvider { constructor(private gasPriceWei: BigNumber) {} @@ -37,9 +38,9 @@ const getTokensDecimals = async (tokenInAddress: Address, tokenOutAddress: Addre export const getRouteUniswapV3ExactInput = async ( args: UniswapV3QuoteExactInputArgs, logger: ComponentLogger -): Promise => { +): Promise<{ route: RouteWithValidQuote; minAmountOut: bigint } | null> => { try { - const { tokenInAddress, tokenOutAddress, amountInRaw } = args; + const { receiver, tokenInAddress, tokenOutAddress, amountInRaw } = args; const { tokenInDecimals, tokenOutDecimals } = await getTokensDecimals(tokenInAddress, tokenOutAddress); const tokenIn = new Token(CHAIN_ID, tokenInAddress, tokenInDecimals); @@ -58,8 +59,8 @@ export const getRouteUniswapV3ExactInput = async ( const amountIn = CurrencyAmount.fromRawAmount(tokenIn, amountInRaw); const options: SwapOptions = { - recipient: CONTRACT_ADDRESSES[CHAIN_ID].DUTCH_AUCTION_REBALANCER, - slippageTolerance: new Percent(100), + recipient: receiver, + slippageTolerance: new Percent(Number(DEX_SLIPPAGE_BPS), 10_000), deadline: Number.MAX_SAFE_INTEGER, type: SwapType.SWAP_ROUTER_02, }; @@ -77,14 +78,17 @@ export const getRouteUniswapV3ExactInput = async ( return null; } - return v3Route; + return { + route: v3Route, + minAmountOut: getDexSlippageAdjustedAmount(BigInt((v3Route?.rawQuote || "0").toString())), + }; } catch (error) { logger.dexQuoteError({ error }, 'Error getting Uniswap V3 route'); return null; } }; -export const prepareUniswapV3SwapCalldata = (assetIn: Address, route: RouteWithValidQuote, inputAmount: bigint, outputAmountMin: bigint): Call[] => { +export const prepareUniswapV3SwapCalldata = (receiver: Address, assetIn: Address, route: RouteWithValidQuote, inputAmount: bigint, outputAmountMin: bigint): Call[] => { const uniswapV3RouterAbi = UniswapSwapRouter02Abi; const approveCalldata = encodeFunctionData({ @@ -100,7 +104,7 @@ export const prepareUniswapV3SwapCalldata = (assetIn: Address, route: RouteWithV functionName: 'exactInput', args: [{ path: encodedPath, - recipient: CONTRACT_ADDRESSES[CHAIN_ID].DUTCH_AUCTION_REBALANCER, // Recipient of the swap is the rebalancer contract + recipient: receiver, amountIn: inputAmount, amountOutMinimum: outputAmountMin, }], diff --git a/src/subscribers/auctionCreated.ts b/src/subscribers/auctionCreated.ts index 14b218f..f69ca79 100644 --- a/src/subscribers/auctionCreated.ts +++ b/src/subscribers/auctionCreated.ts @@ -247,10 +247,13 @@ export const handleAuctionCreatedEvent = async ( const swapParams = await getRebalanceSwapParams({ leverageToken, stakeType, + receiver: CONTRACT_ADDRESSES[CHAIN_ID].DUTCH_AUCTION_REBALANCER, assetIn, assetOut, takeAmount, - requiredAmountIn + requiredAmountIn, + collateralAsset, + debtAsset, }); if (!swapParams.isProfitable) { @@ -354,7 +357,7 @@ export const handleAuctionCreatedEvent = async ( if (receipt.status === "reverted") { const errorString = `Transaction to take auction for LeverageToken ${leverageToken} reverted. takeAmount: ${takeAmount}. Transaction hash: ${tx}`; await sendAlert(`*Error submitting takeAuction transaction*\n${errorString}`, LogLevel.ERROR); - handleAuctionLogger.error({ leverageToken, transactionHash: tx, takeAmount: takeAmount }, "Error submitting takeAuction transaction"); + handleAuctionLogger.error({ leverageToken, transactionHash: tx, takeAmount }, "Error submitting takeAuction transaction"); // We continue trying to take the auction with the next step, since it's likely that the transaction reverted // due to the max take amount decreasing during on-chain execution because of borrow interest or redemptions diff --git a/src/types/index.ts b/src/types/index.ts index 589314c..27dc5f3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -40,6 +40,8 @@ export interface ContractAddresses { FLUID_DEX_RESERVES_RESOLVER?: Address; LEVERAGE_MANAGER: Address; MULTICALL_EXECUTOR: Address; + PENDLE_ROUTER?: Address; + PENDLE_ROUTER_STATIC?: Address; PRE_LIQUIDATION_REBALANCER: Address; WSTETH?: Address; UNISWAP_SWAP_ROUTER_02: Address; @@ -63,6 +65,7 @@ export interface UniswapV2GetAmountsOutArgs { } export interface UniswapV3QuoteExactInputArgs { + receiver: Address; tokenInAddress: Address; tokenOutAddress: Address; amountInRaw: string; @@ -85,10 +88,13 @@ export interface StakeContext { export interface GetRebalanceSwapParamsInput { leverageToken: Address; stakeType: StakeType; + receiver: Address; assetIn: Address; assetOut: Address; takeAmount: bigint; requiredAmountIn: bigint; + collateralAsset: Address; + debtAsset: Address; } export interface Call { @@ -97,6 +103,13 @@ export interface Call { value: bigint; } +export interface GetDexSwapParamsOutput { + isProfitable: boolean; + amountOut: bigint; + minAmountOut: bigint; + swapCalls: Call[]; +} + export interface GetRebalanceSwapParamsOutput { isProfitable: boolean; amountOut: bigint; @@ -104,6 +117,7 @@ export interface GetRebalanceSwapParamsOutput { } export interface GetLIFIQuoteInput { + receiver: Address; fromToken: Address; toToken: Address; fromAmount: bigint; @@ -122,3 +136,29 @@ export interface LeverageTokenRebalanceData { equity: bigint; targetRatio: bigint; } + +export interface GetPendleSwapQuoteInput { + leverageToken: Address; + receiver: Address; + collateralAsset: Address; + debtAsset: Address; + fromAsset: Address; + toAsset: Address; + fromAmount: bigint; +} + +export interface GetPendleSwapQuoteOutput { + amountOut: bigint; + minAmountOut: bigint; + pendleSwapData: { + amountIn: bigint; + assetIn: Address; + data: `0x${string}`; + to: Address; + value: bigint; + }; + underlyingSwapData: GetDexSwapParamsOutput | null; + to: Address; + value: bigint; + prepareCalldata: (quote: GetPendleSwapQuoteOutput) => Call[]; +} diff --git a/src/utils/contractHelpers.ts b/src/utils/contractHelpers.ts index cec2d38..e9b1936 100644 --- a/src/utils/contractHelpers.ts +++ b/src/utils/contractHelpers.ts @@ -13,6 +13,8 @@ import { PreLiquidationRebalancerAbi } from "../../abis/PreLiquidationRebalancer import rebalanceAdapterAbi from "../../abis/RebalanceAdapter"; import { DutchAuctionRebalancerAbi } from "../../abis/DutchAuctionRebalancer"; import uniswapV2Router02Abi from "../../abis/UniswapV2Router02"; +import PendleStaticRouterAbi from "../../abis/PendleStaticRouter"; +import PendleRouterAbi from "../../abis/PendleRouter"; export const getHistoricalLogs = async ({ contractAddress, @@ -179,3 +181,19 @@ export const getPreLiquidationRebalancerContract = () => { client: walletClient, }); }; + +export const getPendleStaticRouterContract = () => { + return getContract({ + address: CONTRACT_ADDRESSES[CHAIN_ID].PENDLE_ROUTER_STATIC as Address, + abi: PendleStaticRouterAbi, + client: walletClient, + }); +}; + +export const getPendleRouterContract = () => { + return getContract({ + address: CONTRACT_ADDRESSES[CHAIN_ID].PENDLE_ROUTER as Address, + abi: PendleRouterAbi, + client: walletClient, + }); +}; diff --git a/src/utils/math.ts b/src/utils/math.ts new file mode 100644 index 0000000..c9fbbc5 --- /dev/null +++ b/src/utils/math.ts @@ -0,0 +1,9 @@ +import { DEX_SLIPPAGE_BPS } from "../constants/values"; + +export const getSlippageAdjustedAmount = (amount: bigint, slippageBps: bigint) => { + return amount * (10000n - slippageBps) / 10000n; +}; + +export const getDexSlippageAdjustedAmount = (amount: bigint) => { + return getSlippageAdjustedAmount(amount, DEX_SLIPPAGE_BPS); +}; \ No newline at end of file diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts new file mode 100644 index 0000000..5feeab3 --- /dev/null +++ b/src/utils/tokens.ts @@ -0,0 +1,51 @@ +import { Address, erc20Abi, getAddress } from "viem"; +import { publicClient } from "./transactionHelpers"; + +const TOKEN_DECIMALS = new Map(); + +export const getTokenDecimals = async (tokenAddresses: Address[]): Promise> => { + + // Find which tokens need to be fetched + const tokensToFetch: Address[] = []; + const result: Record = {}; + + for (const address of tokenAddresses) { + const checksumAddress = getAddress(address); + const cachedDecimals = TOKEN_DECIMALS.get(checksumAddress); + if (cachedDecimals !== undefined) { + result[checksumAddress] = cachedDecimals; + } else { + tokensToFetch.push(checksumAddress); + } + } + + // If all tokens are cached, return early + if (tokensToFetch.length === 0) { + return result; + } + + // Fetch decimals for tokens not in cache using multicall + const multicallResults = await publicClient.multicall({ + contracts: tokensToFetch.map((address) => ({ + address, + abi: erc20Abi, + functionName: "decimals", + })), + }); + + // Process results and update cache + for (let i = 0; i < tokensToFetch.length; i++) { + const multicallResult = multicallResults[i]; + + const decimals = multicallResult.result; + const address = tokensToFetch[i]; + + // Update cache + TOKEN_DECIMALS.set(address, decimals as number); + + // Add to result + result[address] = decimals as number; + } + + return result; +}; \ No newline at end of file