diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..ac957df --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10.6 diff --git a/abi/univ3_nonfungible_position_manager.json b/abi/univ3_nonfungible_position_manager.json new file mode 100644 index 0000000..720db54 --- /dev/null +++ b/abi/univ3_nonfungible_position_manager.json @@ -0,0 +1,686 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_factory", "type": "address" }, + { "internalType": "address", "name": "_WETH9", "type": "address" }, + { + "internalType": "address", + "name": "_tokenDescriptor_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "DecreaseLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "IncreaseLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WETH9", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseURI", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "burn", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { + "internalType": "uint128", + "name": "amount0Max", + "type": "uint128" + }, + { "internalType": "uint128", "name": "amount1Max", "type": "uint128" } + ], + "internalType": "struct INonfungiblePositionManager.CollectParams", + "name": "params", + "type": "tuple" + } + ], + "name": "collect", + "outputs": [ + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160" } + ], + "name": "createAndInitializePoolIfNecessary", + "outputs": [ + { "internalType": "address", "name": "pool", "type": "address" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { "internalType": "uint128", "name": "liquidity", "type": "uint128" }, + { + "internalType": "uint256", + "name": "amount0Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Min", + "type": "uint256" + }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "internalType": "struct INonfungiblePositionManager.DecreaseLiquidityParams", + "name": "params", + "type": "tuple" + } + ], + "name": "decreaseLiquidity", + "outputs": [ + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "getApproved", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { + "internalType": "uint256", + "name": "amount0Desired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Desired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount0Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Min", + "type": "uint256" + }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "internalType": "struct INonfungiblePositionManager.IncreaseLiquidityParams", + "name": "params", + "type": "tuple" + } + ], + "name": "increaseLiquidity", + "outputs": [ + { "internalType": "uint128", "name": "liquidity", "type": "uint128" }, + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "operator", "type": "address" } + ], + "name": "isApprovedForAll", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "int24", "name": "tickLower", "type": "int24" }, + { "internalType": "int24", "name": "tickUpper", "type": "int24" }, + { + "internalType": "uint256", + "name": "amount0Desired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Desired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount0Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Min", + "type": "uint256" + }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "internalType": "struct INonfungiblePositionManager.MintParams", + "name": "params", + "type": "tuple" + } + ], + "name": "mint", + "outputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { "internalType": "uint128", "name": "liquidity", "type": "uint128" }, + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes[]", "name": "data", "type": "bytes[]" } + ], + "name": "multicall", + "outputs": [ + { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "ownerOf", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "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": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "positions", + "outputs": [ + { "internalType": "uint96", "name": "nonce", "type": "uint96" }, + { "internalType": "address", "name": "operator", "type": "address" }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "int24", "name": "tickLower", "type": "int24" }, + { "internalType": "int24", "name": "tickUpper", "type": "int24" }, + { "internalType": "uint128", "name": "liquidity", "type": "uint128" }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + }, + { "internalType": "uint128", "name": "tokensOwed0", "type": "uint128" }, + { "internalType": "uint128", "name": "tokensOwed1", "type": "uint128" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "refundETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { "internalType": "bytes", "name": "_data", "type": "bytes" } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "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": "selfPermit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "expiry", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "selfPermitAllowed", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "expiry", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "selfPermitAllowedIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "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": "selfPermitIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "operator", "type": "address" }, + { "internalType": "bool", "name": "approved", "type": "bool" } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } + ], + "name": "supportsInterface", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amountMinimum", "type": "uint256" }, + { "internalType": "address", "name": "recipient", "type": "address" } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "index", "type": "uint256" } + ], + "name": "tokenByIndex", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "uint256", "name": "index", "type": "uint256" } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "tokenURI", + "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": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount0Owed", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1Owed", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "uniswapV3MintCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amountMinimum", "type": "uint256" }, + { "internalType": "address", "name": "recipient", "type": "address" } + ], + "name": "unwrapWETH9", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/abi/univ3_pool.json b/abi/univ3_pool.json new file mode 100644 index 0000000..773ce15 --- /dev/null +++ b/abi/univ3_pool.json @@ -0,0 +1,692 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "CollectProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid1", + "type": "uint256" + } + ], + "name": "Flash", + "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": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0New", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1New", + "type": "uint8" + } + ], + "name": "SetFeeProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [ + { "internalType": "int24", "name": "tickLower", "type": "int24" }, + { "internalType": "int24", "name": "tickUpper", "type": "int24" }, + { "internalType": "uint128", "name": "amount", "type": "uint128" } + ], + "name": "burn", + "outputs": [ + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "int24", "name": "tickLower", "type": "int24" }, + { "internalType": "int24", "name": "tickUpper", "type": "int24" }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collect", + "outputs": [ + { "internalType": "uint128", "name": "amount0", "type": "uint128" }, + { "internalType": "uint128", "name": "amount1", "type": "uint128" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collectProtocol", + "outputs": [ + { "internalType": "uint128", "name": "amount0", "type": "uint128" }, + { "internalType": "uint128", "name": "amount1", "type": "uint128" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fee", + "outputs": [{ "internalType": "uint24", "name": "", "type": "uint24" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal0X128", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal1X128", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "flash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + } + ], + "name": "increaseObservationCardinalityNext", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidity", + "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidityPerTick", + "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "int24", "name": "tickLower", "type": "int24" }, + { "internalType": "int24", "name": "tickUpper", "type": "int24" }, + { "internalType": "uint128", "name": "amount", "type": "uint128" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "mint", + "outputs": [ + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "observations", + "outputs": [ + { "internalType": "uint32", "name": "blockTimestamp", "type": "uint32" }, + { "internalType": "int56", "name": "tickCumulative", "type": "int56" }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { "internalType": "bool", "name": "initialized", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint32[]", "name": "secondsAgos", "type": "uint32[]" } + ], + "name": "observe", + "outputs": [ + { + "internalType": "int56[]", + "name": "tickCumulatives", + "type": "int56[]" + }, + { + "internalType": "uint160[]", + "name": "secondsPerLiquidityCumulativeX128s", + "type": "uint160[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "positions", + "outputs": [ + { "internalType": "uint128", "name": "liquidity", "type": "uint128" }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + }, + { "internalType": "uint128", "name": "tokensOwed0", "type": "uint128" }, + { "internalType": "uint128", "name": "tokensOwed1", "type": "uint128" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFees", + "outputs": [ + { "internalType": "uint128", "name": "token0", "type": "uint128" }, + { "internalType": "uint128", "name": "token1", "type": "uint128" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "feeProtocol0", "type": "uint8" }, + { "internalType": "uint8", "name": "feeProtocol1", "type": "uint8" } + ], + "name": "setFeeProtocol", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "slot0", + "outputs": [ + { "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160" }, + { "internalType": "int24", "name": "tick", "type": "int24" }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { "internalType": "uint8", "name": "feeProtocol", "type": "uint8" }, + { "internalType": "bool", "name": "unlocked", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "int24", "name": "tickLower", "type": "int24" }, + { "internalType": "int24", "name": "tickUpper", "type": "int24" } + ], + "name": "snapshotCumulativesInside", + "outputs": [ + { + "internalType": "int56", + "name": "tickCumulativeInside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityInsideX128", + "type": "uint160" + }, + { "internalType": "uint32", "name": "secondsInside", "type": "uint32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "bool", "name": "zeroForOne", "type": "bool" }, + { "internalType": "int256", "name": "amountSpecified", "type": "int256" }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "swap", + "outputs": [ + { "internalType": "int256", "name": "amount0", "type": "int256" }, + { "internalType": "int256", "name": "amount1", "type": "int256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "int16", "name": "", "type": "int16" }], + "name": "tickBitmap", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tickSpacing", + "outputs": [{ "internalType": "int24", "name": "", "type": "int24" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "int24", "name": "", "type": "int24" }], + "name": "ticks", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { "internalType": "int128", "name": "liquidityNet", "type": "int128" }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { "internalType": "uint32", "name": "secondsOutside", "type": "uint32" }, + { "internalType": "bool", "name": "initialized", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/constants/integration_token.py b/constants/integration_token.py index 5fe4ce2..317bcb7 100644 --- a/constants/integration_token.py +++ b/constants/integration_token.py @@ -5,3 +5,4 @@ class Token(Enum): USDE = 'USDe' SUSDE = 'sUSDe' ENA = 'ENA' + SENA = 'sENA' \ No newline at end of file diff --git a/constants/rumpel.py b/constants/rumpel.py new file mode 100644 index 0000000..da96f81 --- /dev/null +++ b/constants/rumpel.py @@ -0,0 +1,26 @@ +import json +from web3 import Web3 +from utils.web3_utils import w3 + +KPSAT3_ADDRESS = Web3.to_checksum_address("0x263b0f5e179c1d72B884C43105C620d2112dF2a0") +SENA_ADDRESS = Web3.to_checksum_address("0x8bE3460A480c80728a8C4D7a5D5303c85ba7B3b9") + +KPSATS3_SENA_UNIV3_POOL_DEPLOYED_BLOCK = 21383217; +KPSATS3_SENA_UNIV3_POOL_ADDRESS = Web3.to_checksum_address("0x5D29647b684Ce835F442915cC3C8e99aAb2A26C6") +UNIV3_NONFUNGUBLE_POSITION_MANAGER_ADDRESS = Web3.to_checksum_address("0xC36442b4a4522E871399CD717aBDD847Ab11FE88") + +with open("abi/univ3_nonfungible_position_manager.json") as f: + UNIV3_NONFUNGIBLE_POSITION_MANAGER_ABI = json.load(f) + +UNIV3_NONFUNGIBLE_POSITION_MANAGER_CONTRACT = w3.eth.contract( + address=UNIV3_NONFUNGUBLE_POSITION_MANAGER_ADDRESS, + abi=UNIV3_NONFUNGIBLE_POSITION_MANAGER_ABI, +) + +with open("abi/univ3_pool.json") as f: + UNIV3_POOL_ABI = json.load(f) + +KPSATS3_SENA_UNIV3_POOL_CONTRACT = w3.eth.contract( + address=KPSATS3_SENA_UNIV3_POOL_ADDRESS, + abi=UNIV3_POOL_ABI, +) diff --git a/integrations/cork_susde.py b/integrations/cork_susde.py index 955e2d6..f9ec780 100644 --- a/integrations/cork_susde.py +++ b/integrations/cork_susde.py @@ -23,7 +23,13 @@ from integrations.cached_balances_integration import CachedBalancesIntegration from integrations.integration_ids import IntegrationID -from utils.web3_utils import fetch_events_logs_with_retry, multicall, W3_BY_CHAIN +from utils.web3_utils import ( + MULTICALL_ADDRESS_BY_CHAIN, + fetch_events_logs_with_retry, + multicall, + W3_BY_CHAIN, + multicall_by_address, +) ######################################################################## # Terminologies @@ -42,12 +48,14 @@ QuoteTokenAddress = NewType("QuoteTokenAddress", ChecksumAddress) VaultShareTokenAddress = NewType("VaultShareTokenAddress", ChecksumAddress) + class TermConfig(NamedTuple): share_token_addr: PsmShareTokenAddress amm_lp_token_addr: Optional[LpTokenAddress] amm_pool_addr: ChecksumAddress start_block: int + class PairConfig(NamedTuple): eligible_asset: TokenType amm_quote_token_addr: QuoteTokenAddress @@ -56,16 +64,18 @@ class PairConfig(NamedTuple): start_block: int terms: Dict[TermId, TermConfig] + @dataclass class PooledBalance: - ''' + """ PooledBalance is a class that represents the balances of a pool or ERC4626 vault. It contains the following attributes: - total_assets: the total amount of the eligible asset in the pool - total_assets_by_token: a dictionary of the total amount of the other assets in the pool - total_supply: the total supply of the shares - shares_by_account: a dictionary that maps each account address to its share of the pool - ''' + """ + pair_config: PairConfig term_config: Optional[TermConfig] = None total_assets: Union[int, Tuple[int, ...]] = 0 @@ -74,15 +84,13 @@ class PooledBalance: shares_by_account: Dict[ChecksumAddress, int] = field(default_factory=dict) -class CorkIntegration( - CachedBalancesIntegration -): +class CorkIntegration(CachedBalancesIntegration): def __init__( self, integration_id: IntegrationID, eligible_token_addr: ChecksumAddress, start_block: int, - chain: Chain = Chain.ETHEREUM, + chain: Chain = Chain.SEPOLIA, summary_cols: Optional[List[SummaryColumn]] = None, reward_multiplier: int = 1, balance_multiplier: int = 1, @@ -109,28 +117,34 @@ def __init__( self.pair_config_by_id: Dict[bytes, PairConfig] = None self.psm_contract = PSM_CONTRACT_BY_CHAIN[self.chain] - self.psm_balances_by_share_token: Dict[PsmShareTokenAddress, PooledBalance] = None + self.psm_balances_by_share_token: Dict[PsmShareTokenAddress, PooledBalance] = ( + None + ) self.amm_contract = AMM_CONTRACT_BY_CHAIN[self.chain] self.amm_pool_addr: Optional[ChecksumAddress] = None self.amm_balances_by_lp_token: Dict[LpTokenAddress, PooledBalance] = None - self.vault_balances_by_vault_share_token: Dict[VaultShareTokenAddress, PooledBalance] = None - + self.vault_balances_by_vault_share_token: Dict[ + VaultShareTokenAddress, PooledBalance + ] = None def update_pair_config( self, pair_config_by_id: Dict[bytes, PairConfig], - from_block: int = 0, - to_block: int | str = "latest" + from_block: Optional[int] = None, + to_block: int | str = "latest", ) -> Dict[bytes, PairConfig]: # Fetch events that indicates new pair was created + print("from_block", from_block) + print("self.start_block", self.start_block) + print("to_block", to_block) new_pair_events_with_eligible_pa = fetch_events_logs_with_retry( "Pairs initialized with USDe as PA", self.psm_contract.events.InitializedModuleCore(), from_block or self.start_block, to_block, - filter = { + filter={ "pa": self.eligible_token_addr, }, ) @@ -139,7 +153,7 @@ def update_pair_config( self.psm_contract.events.InitializedModuleCore(), from_block or self.start_block, to_block, - filter = { + filter={ "ra": self.eligible_token_addr, }, ) @@ -155,29 +169,36 @@ def update_pair_config( # ) # Set new pairs to config - pair_config_by_id.update({ - event["args"]["id"]: PairConfig( - eligible_asset=TokenType.PA, - amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), - vault_share_token_addr=Web3.to_checksum_address(event["args"]["lv"]), - vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], - start_block=event["blockNumber"], - terms={}, - ) - for event in new_pair_events_with_eligible_pa - # if event["args"]["pa"] == self.eligible_token_addr - } | { - event["args"]["id"]: PairConfig( - eligible_asset=TokenType.RA, - amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), - vault_share_token_addr=Web3.to_checksum_address(event["args"]["lv"]), - vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], - start_block=event["blockNumber"], - terms={}, - ) - for event in new_pair_events_with_eligible_ra - # if event["args"]["ra"] == self.eligible_token_addr - }) + pair_config_by_id.update( + { + event["args"]["id"]: PairConfig( + eligible_asset=TokenType.PA, + amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), + vault_share_token_addr=Web3.to_checksum_address( + event["args"]["lv"] + ), + vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], + start_block=event["blockNumber"], + terms={}, + ) + for event in new_pair_events_with_eligible_pa + # if event["args"]["pa"] == self.eligible_token_addr + } + | { + event["args"]["id"]: PairConfig( + eligible_asset=TokenType.RA, + amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), + vault_share_token_addr=Web3.to_checksum_address( + event["args"]["lv"] + ), + vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], + start_block=event["blockNumber"], + terms={}, + ) + for event in new_pair_events_with_eligible_ra + # if event["args"]["ra"] == self.eligible_token_addr + } + ) # Uniswap v4 Native Liquidity Modification is disabled on Cork AMM pools, # which also prevents the creation of LP tokens (NFT) by the Uniswap V4 Position Manager. @@ -220,7 +241,7 @@ def update_pair_config( self.psm_contract.events.Issued(), pair_config.start_block or self.start_block, to_block, - filter = { + filter={ "id": pair_id, }, ) @@ -268,12 +289,11 @@ def update_pair_config( pair_config.terms[term_id] = term_config return pair_config_by_id - def update_psm_pool_balances( self, pool_balances: Dict[PsmShareTokenAddress, PooledBalance], from_block: int = 0, - to_block: int | str = "latest" + to_block: int | str = "latest", ) -> Dict[PsmShareTokenAddress, PooledBalance]: # For each pair... for pair_id, pair_config in self.pair_config_by_id.items(): @@ -292,7 +312,7 @@ def update_psm_pool_balances( self.psm_contract.events.PsmDeposited(), start_block, to_block, - filter = { + filter={ "id": pair_id, "dsId": int(term_id), }, @@ -306,7 +326,7 @@ def update_psm_pool_balances( self.psm_contract.events.CtRedeemed(), start_block, to_block, - filter = { + filter={ "id": pair_id, "dsId": int(term_id), }, @@ -320,7 +340,7 @@ def update_psm_pool_balances( self.psm_contract.events.DsRedeemed(), start_block, to_block, - filter = { + filter={ "id": pair_id, "dsId": int(term_id), }, @@ -334,7 +354,7 @@ def update_psm_pool_balances( self.psm_contract.events.Repurchased(), start_block, to_block, - filter = { + filter={ "id": pair_id, "dsId": int(term_id), }, @@ -344,27 +364,29 @@ def update_psm_pool_balances( # Includes Rollover Events if pair_config.eligible_asset == TokenType.RA: balance_in = sum( - [event["args"]["amount"] for event in deposit_events] + - [event["args"]["raUsed"] for event in repurchase_events] + [event["args"]["amount"] for event in deposit_events] + + [event["args"]["raUsed"] for event in repurchase_events] ) balance_out = sum( - [event["args"]["raReceived"] for event in withdraw_events] + - [event["args"]["raReceived"] for event in redeem_events] + [event["args"]["raReceived"] for event in withdraw_events] + + [event["args"]["raReceived"] for event in redeem_events] ) elif pair_config.eligible_asset == TokenType.PA: balance_in = sum( [event["args"]["paUsed"] for event in redeem_events] ) balance_out = sum( - [event["args"]["paReceived"] for event in withdraw_events] + - [event["args"]["receivedPa"] for event in repurchase_events] + [event["args"]["paReceived"] for event in withdraw_events] + + [event["args"]["receivedPa"] for event in repurchase_events] ) else: raise NotImplementedError("Token type not yet implemented") # Get pooled balances of each PSM share_token_addr = term_config.share_token_addr - pool_balances.setdefault(share_token_addr, PooledBalance(pair_config, term_config)) + pool_balances.setdefault( + share_token_addr, PooledBalance(pair_config, term_config) + ) pool = pool_balances[share_token_addr] # Update asset balance of PSM pool @@ -405,12 +427,11 @@ def update_psm_pool_balances( pool.shares_by_account[recipient] += value return pool_balances - def update_amm_pool_balances( self, pool_balances: Dict[LpTokenAddress, PooledBalance], from_block: int = 0, - to_block: int | str = "latest" + to_block: int | str = "latest", ) -> Dict[LpTokenAddress, PooledBalance]: # For each pair... for _pair_id, pair_config in self.pair_config_by_id.items(): @@ -420,7 +441,9 @@ def update_amm_pool_balances( # Get pooled balances of each AMM pair lp_token_addr = term_config.amm_lp_token_addr - pool_balances.setdefault(lp_token_addr, PooledBalance(pair_config, term_config)) + pool_balances.setdefault( + lp_token_addr, PooledBalance(pair_config, term_config) + ) pool = pool_balances[lp_token_addr] # For each token, accumulate all balance changes from Transfer events... @@ -458,12 +481,11 @@ def update_amm_pool_balances( pool.shares_by_account[recipient] += value return pool_balances - def update_vault_pool_balances( self, pool_balances: Dict[VaultShareTokenAddress, PooledBalance], from_block: int = 0, - to_block: int | str = "latest" + to_block: int | str = "latest", ) -> Dict[VaultShareTokenAddress, PooledBalance]: # For each pair... for _pair_id, pair_config in self.pair_config_by_id.items(): @@ -508,7 +530,6 @@ def update_vault_pool_balances( pool.shares_by_account[recipient] += value return pool_balances - def get_block_balances( self, cached_data: Dict[int, Dict[ChecksumAddress, float]], blocks: List[int] ) -> Dict[int, Dict[ChecksumAddress, float]]: @@ -534,7 +555,9 @@ def get_block_balances( return new_block_data sorted_blocks = sorted(blocks) - cache_copy_of_account_bals: Dict[int, Dict[ChecksumAddress, float]] = deepcopy(cached_data) + cache_copy_of_account_bals: Dict[int, Dict[ChecksumAddress, float]] = deepcopy( + cached_data + ) for block in sorted_blocks: # find the closest prev block in the data @@ -558,22 +581,29 @@ def get_block_balances( # Fetch pair config at prev_block if not already done if self.pair_config_by_id is None: - self.pair_config_by_id = self.update_pair_config({}, to_block=prev_block) + self.pair_config_by_id = self.update_pair_config( + {}, from_block=prev_block, to_block=start + ) # Fetch Peg Stability term balances at prev_block if not already done if self.psm_balances_by_share_token is None: self.psm_balances_by_share_token = self.update_psm_pool_balances( - {}, to_block=prev_block) + {}, from_block=prev_block, to_block=start + ) # Fetch AMM Liquidity Pool term balances at prev_block if not already done if self.amm_balances_by_lp_token is None: self.amm_balances_by_lp_token = self.update_amm_pool_balances( - {}, to_block=prev_block) + {}, from_block=prev_block, to_block=start + ) # Fetch Vault term balances at prev_block if not already done if self.vault_balances_by_vault_share_token is None: - self.vault_balances_by_vault_share_token = self.update_vault_pool_balances( - {}, to_block=prev_block) + self.vault_balances_by_vault_share_token = ( + self.update_vault_pool_balances( + {}, from_block=prev_block, to_block=start + ) + ) # parse events since and update bals while start <= block: @@ -584,16 +614,18 @@ def get_block_balances( self.update_pair_config(self.pair_config_by_id, start, to_block) # Update PSM Pool term balances - self.update_psm_pool_balances(self.psm_balances_by_share_token, start, to_block) + self.update_psm_pool_balances( + self.psm_balances_by_share_token, start, to_block + ) # Update AMM Pool term balances - self.update_amm_pool_balances(self.amm_balances_by_lp_token, start, to_block) + self.update_amm_pool_balances( + self.amm_balances_by_lp_token, start, to_block + ) # Update Vault Liquidity Pool term balances self.update_vault_pool_balances( - self.vault_balances_by_vault_share_token, - start, - to_block + self.vault_balances_by_vault_share_token, start, to_block ) start = to_block + 1 @@ -615,17 +647,24 @@ def get_block_balances( amm_contract_function.fn_name, [ amm_pool.pair_config.amm_quote_token_addr, - amm_pool.term_config.share_token_addr + amm_pool.term_config.share_token_addr, ], ) for amm_pool in self.amm_balances_by_lp_token.values() ] - multicall_results = multicall(self.w3, amm_calls, block) + multicall_results = multicall_by_address( + w3=self.w3, + multical_address=MULTICALL_ADDRESS_BY_CHAIN[self.chain], + calls=amm_calls, + block_identifier=block, + ) # The results contain the following: # - The `result[0]` is the total balance of the asset token in the AMM pool # - The `result[1]` is the total balance of the share token in the AMM pool - for amm_pool, result in zip(self.amm_balances_by_lp_token.values(), multicall_results): + for amm_pool, result in zip( + self.amm_balances_by_lp_token.values(), multicall_results + ): # Update the total Ethena-asset and PSM-shares balance of each AMM pool amm_pool.total_assets = (result[0], result[1]) @@ -647,18 +686,29 @@ def get_block_balances( # If the account_addr is the vault_addr, then we need to attribute the # Ethena asset balances to the respective LV token holders if account_addr == amm_pool.pair_config.vault_addr: - vault_share_token_addr = amm_pool.pair_config.vault_share_token_addr - vault = self.vault_balances_by_vault_share_token[vault_share_token_addr] - for account_addr, account_shares in vault.shares_by_account.items(): + vault_share_token_addr = ( + amm_pool.pair_config.vault_share_token_addr + ) + vault = self.vault_balances_by_vault_share_token[ + vault_share_token_addr + ] + for ( + account_addr, + account_shares, + ) in vault.shares_by_account.items(): account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + account_bals[account_addr] = Decimal( + account_bals[account_addr] + ) + ( amount * Decimal(account_shares) / Decimal(vault.total_supply) ) else: account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + amount + account_bals[account_addr] = ( + Decimal(account_bals[account_addr]) + amount + ) # # Attribute PSM share token balances to their respective LP token holders # if amm_pool.term_config is not None: @@ -685,11 +735,20 @@ def get_block_balances( # If the account_addr is the Cork Vault address, then we need to attribute the # Ethena asset balances to the respective LV token holders if account_addr == psm_pool.pair_config.vault_addr: - vault_share_token_addr = psm_pool.pair_config.vault_share_token_addr - vault = self.vault_balances_by_vault_share_token[vault_share_token_addr] - for account_addr, account_shares in vault.shares_by_account.items(): + vault_share_token_addr = ( + psm_pool.pair_config.vault_share_token_addr + ) + vault = self.vault_balances_by_vault_share_token[ + vault_share_token_addr + ] + for ( + account_addr, + account_shares, + ) in vault.shares_by_account.items(): account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + account_bals[account_addr] = Decimal( + account_bals[account_addr] + ) + ( amount * Decimal(account_shares) / Decimal(vault.total_supply) @@ -699,6 +758,7 @@ def get_block_balances( elif account_addr == psm_pool.term_config.amm_pool_addr: lp_token_addr = psm_pool.term_config.amm_lp_token_addr amm_pool = self.amm_balances_by_lp_token[lp_token_addr] + # If there are other Uniswap V4 pools which manage PSM-shares, # the total amount of PSM-shares at the UniV4 PoolManager address # will exceed the amount present in the Cork AMM pool, @@ -709,16 +769,24 @@ def get_block_balances( * Decimal(amm_pool.total_assets[1]) / Decimal(psm_pool.total_supply) ) - for account_addr, account_shares in amm_pool.shares_by_account.items(): + + for ( + account_addr, + account_shares, + ) in amm_pool.shares_by_account.items(): account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + account_bals[account_addr] = Decimal( + account_bals[account_addr] + ) + ( amount * Decimal(account_shares) / Decimal(amm_pool.total_supply) ) else: account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + amount + account_bals[account_addr] = ( + Decimal(account_bals[account_addr]) + amount + ) # Round off to 4 decimals for account_addr, account_bal in account_bals.items(): @@ -744,10 +812,11 @@ def get_block_balances( ) print("=" * 120) - print("Run without cached data", + print( + "Run without cached data", cork_integration.get_block_balances( - cached_data={}, blocks=[20000000, 20000001, 20000002] - ) + cached_data={}, blocks=[7686000, 7686001, 7686002] + ), ) # Example output: # { @@ -757,20 +826,21 @@ def get_block_balances( # } print("=" * 120, "\n" * 5) - print("Run with cached data", + print( + "Run with cached data", cork_integration.get_block_balances( cached_data={ - 20000000: { - Web3.to_checksum_address("0x123"): 100, - Web3.to_checksum_address("0x456"): 200, + 7686000: { + Web3.to_checksum_address("0x0000000000000000000000000000000000000000"): 100, + Web3.to_checksum_address("0x0000000000000000000000000000000000000001"): 200, }, - 20000001: { - Web3.to_checksum_address("0x123"): 101, - Web3.to_checksum_address("0x456"): 201, + 7686001: { + Web3.to_checksum_address("0x0000000000000000000000000000000000000000"): 101, + Web3.to_checksum_address("0x0000000000000000000000000000000000000001"): 201, }, }, - blocks=[20000002], - ) + blocks=[7686002], + ), ) print("=" * 120) # Example output: diff --git a/integrations/cork_usde.py b/integrations/cork_usde.py index dc8269b..5f5b86a 100644 --- a/integrations/cork_usde.py +++ b/integrations/cork_usde.py @@ -42,12 +42,14 @@ QuoteTokenAddress = NewType("QuoteTokenAddress", ChecksumAddress) VaultShareTokenAddress = NewType("VaultShareTokenAddress", ChecksumAddress) + class TermConfig(NamedTuple): share_token_addr: PsmShareTokenAddress amm_lp_token_addr: Optional[LpTokenAddress] amm_pool_addr: ChecksumAddress start_block: int + class PairConfig(NamedTuple): eligible_asset: TokenType amm_quote_token_addr: QuoteTokenAddress @@ -56,16 +58,18 @@ class PairConfig(NamedTuple): start_block: int terms: Dict[TermId, TermConfig] + @dataclass class PooledBalance: - ''' + """ PooledBalance is a class that represents the balances of a pool or ERC4626 vault. It contains the following attributes: - total_assets: the total amount of the eligible asset in the pool - total_assets_by_token: a dictionary of the total amount of the other assets in the pool - total_supply: the total supply of the shares - shares_by_account: a dictionary that maps each account address to its share of the pool - ''' + """ + pair_config: PairConfig term_config: Optional[TermConfig] = None total_assets: Union[int, Tuple[int, ...]] = 0 @@ -74,15 +78,13 @@ class PooledBalance: shares_by_account: Dict[ChecksumAddress, int] = field(default_factory=dict) -class CorkIntegration( - CachedBalancesIntegration -): +class CorkIntegration(CachedBalancesIntegration): def __init__( self, integration_id: IntegrationID, eligible_token_addr: ChecksumAddress, start_block: int, - chain: Chain = Chain.ETHEREUM, + chain: Chain = Chain.SEPOLIA, summary_cols: Optional[List[SummaryColumn]] = None, reward_multiplier: int = 1, balance_multiplier: int = 1, @@ -92,16 +94,16 @@ def __init__( ethereal_multiplier_func: Optional[Callable[[int, str], int]] = None, ): super().__init__( - integration_id, - start_block, - chain, - summary_cols, - reward_multiplier, - balance_multiplier, - excluded_addresses, - end_block, - ethereal_multiplier, - ethereal_multiplier_func, + integration_id=integration_id, + start_block=start_block, + chain=chain, + summary_cols=summary_cols, + reward_multiplier=reward_multiplier, + balance_multiplier=balance_multiplier, + excluded_addresses=excluded_addresses, + end_block=end_block, + ethereal_multiplier=ethereal_multiplier, + ethereal_multiplier_func=ethereal_multiplier_func, ) self.w3 = W3_BY_CHAIN[self.chain]["w3"] @@ -109,20 +111,23 @@ def __init__( self.pair_config_by_id: Dict[bytes, PairConfig] = None self.psm_contract = PSM_CONTRACT_BY_CHAIN[self.chain] - self.psm_balances_by_share_token: Dict[PsmShareTokenAddress, PooledBalance] = None + self.psm_balances_by_share_token: Dict[PsmShareTokenAddress, PooledBalance] = ( + None + ) self.amm_contract = AMM_CONTRACT_BY_CHAIN[self.chain] self.amm_pool_addr: Optional[ChecksumAddress] = None self.amm_balances_by_lp_token: Dict[LpTokenAddress, PooledBalance] = None - self.vault_balances_by_vault_share_token: Dict[VaultShareTokenAddress, PooledBalance] = None - + self.vault_balances_by_vault_share_token: Dict[ + VaultShareTokenAddress, PooledBalance + ] = None def update_pair_config( self, pair_config_by_id: Dict[bytes, PairConfig], from_block: int = 0, - to_block: int | str = "latest" + to_block: int | str = "latest", ) -> Dict[bytes, PairConfig]: # Fetch events that indicates new pair was created new_pair_events_with_eligible_pa = fetch_events_logs_with_retry( @@ -130,7 +135,7 @@ def update_pair_config( self.psm_contract.events.InitializedModuleCore(), from_block or self.start_block, to_block, - filter = { + filter={ "pa": self.eligible_token_addr, }, ) @@ -139,7 +144,7 @@ def update_pair_config( self.psm_contract.events.InitializedModuleCore(), from_block or self.start_block, to_block, - filter = { + filter={ "ra": self.eligible_token_addr, }, ) @@ -155,29 +160,36 @@ def update_pair_config( # ) # Set new pairs to config - pair_config_by_id.update({ - event["args"]["id"]: PairConfig( - eligible_asset=TokenType.PA, - amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), - vault_share_token_addr=Web3.to_checksum_address(event["args"]["lv"]), - vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], - start_block=event["blockNumber"], - terms={}, - ) - for event in new_pair_events_with_eligible_pa - # if event["args"]["pa"] == self.eligible_token_addr - } | { - event["args"]["id"]: PairConfig( - eligible_asset=TokenType.RA, - amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), - vault_share_token_addr=Web3.to_checksum_address(event["args"]["lv"]), - vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], - start_block=event["blockNumber"], - terms={}, - ) - for event in new_pair_events_with_eligible_ra - # if event["args"]["ra"] == self.eligible_token_addr - }) + pair_config_by_id.update( + { + event["args"]["id"]: PairConfig( + eligible_asset=TokenType.PA, + amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), + vault_share_token_addr=Web3.to_checksum_address( + event["args"]["lv"] + ), + vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], + start_block=event["blockNumber"], + terms={}, + ) + for event in new_pair_events_with_eligible_pa + # if event["args"]["pa"] == self.eligible_token_addr + } + | { + event["args"]["id"]: PairConfig( + eligible_asset=TokenType.RA, + amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), + vault_share_token_addr=Web3.to_checksum_address( + event["args"]["lv"] + ), + vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], + start_block=event["blockNumber"], + terms={}, + ) + for event in new_pair_events_with_eligible_ra + # if event["args"]["ra"] == self.eligible_token_addr + } + ) # Uniswap v4 Native Liquidity Modification is disabled on Cork AMM pools, # which also prevents the creation of LP tokens (NFT) by the Uniswap V4 Position Manager. @@ -220,7 +232,7 @@ def update_pair_config( self.psm_contract.events.Issued(), pair_config.start_block or self.start_block, to_block, - filter = { + filter={ "id": pair_id, }, ) @@ -268,12 +280,11 @@ def update_pair_config( pair_config.terms[term_id] = term_config return pair_config_by_id - def update_psm_pool_balances( self, pool_balances: Dict[PsmShareTokenAddress, PooledBalance], from_block: int = 0, - to_block: int | str = "latest" + to_block: int | str = "latest", ) -> Dict[PsmShareTokenAddress, PooledBalance]: # For each pair... for pair_id, pair_config in self.pair_config_by_id.items(): @@ -292,7 +303,7 @@ def update_psm_pool_balances( self.psm_contract.events.PsmDeposited(), start_block, to_block, - filter = { + filter={ "id": pair_id, "dsId": int(term_id), }, @@ -306,7 +317,7 @@ def update_psm_pool_balances( self.psm_contract.events.CtRedeemed(), start_block, to_block, - filter = { + filter={ "id": pair_id, "dsId": int(term_id), }, @@ -320,7 +331,7 @@ def update_psm_pool_balances( self.psm_contract.events.DsRedeemed(), start_block, to_block, - filter = { + filter={ "id": pair_id, "dsId": int(term_id), }, @@ -334,7 +345,7 @@ def update_psm_pool_balances( self.psm_contract.events.Repurchased(), start_block, to_block, - filter = { + filter={ "id": pair_id, "dsId": int(term_id), }, @@ -344,27 +355,29 @@ def update_psm_pool_balances( # Includes Rollover Events if pair_config.eligible_asset == TokenType.RA: balance_in = sum( - [event["args"]["amount"] for event in deposit_events] + - [event["args"]["raUsed"] for event in repurchase_events] + [event["args"]["amount"] for event in deposit_events] + + [event["args"]["raUsed"] for event in repurchase_events] ) balance_out = sum( - [event["args"]["raReceived"] for event in withdraw_events] + - [event["args"]["raReceived"] for event in redeem_events] + [event["args"]["raReceived"] for event in withdraw_events] + + [event["args"]["raReceived"] for event in redeem_events] ) elif pair_config.eligible_asset == TokenType.PA: balance_in = sum( [event["args"]["paUsed"] for event in redeem_events] ) balance_out = sum( - [event["args"]["paReceived"] for event in withdraw_events] + - [event["args"]["receivedPa"] for event in repurchase_events] + [event["args"]["paReceived"] for event in withdraw_events] + + [event["args"]["receivedPa"] for event in repurchase_events] ) else: raise NotImplementedError("Token type not yet implemented") # Get pooled balances of each PSM share_token_addr = term_config.share_token_addr - pool_balances.setdefault(share_token_addr, PooledBalance(pair_config, term_config)) + pool_balances.setdefault( + share_token_addr, PooledBalance(pair_config, term_config) + ) pool = pool_balances[share_token_addr] # Update asset balance of PSM pool @@ -405,12 +418,11 @@ def update_psm_pool_balances( pool.shares_by_account[recipient] += value return pool_balances - def update_amm_pool_balances( self, pool_balances: Dict[LpTokenAddress, PooledBalance], from_block: int = 0, - to_block: int | str = "latest" + to_block: int | str = "latest", ) -> Dict[LpTokenAddress, PooledBalance]: # For each pair... for _pair_id, pair_config in self.pair_config_by_id.items(): @@ -420,7 +432,9 @@ def update_amm_pool_balances( # Get pooled balances of each AMM pair lp_token_addr = term_config.amm_lp_token_addr - pool_balances.setdefault(lp_token_addr, PooledBalance(pair_config, term_config)) + pool_balances.setdefault( + lp_token_addr, PooledBalance(pair_config, term_config) + ) pool = pool_balances[lp_token_addr] # For each token, accumulate all balance changes from Transfer events... @@ -458,12 +472,11 @@ def update_amm_pool_balances( pool.shares_by_account[recipient] += value return pool_balances - def update_vault_pool_balances( self, pool_balances: Dict[VaultShareTokenAddress, PooledBalance], from_block: int = 0, - to_block: int | str = "latest" + to_block: int | str = "latest", ) -> Dict[VaultShareTokenAddress, PooledBalance]: # For each pair... for _pair_id, pair_config in self.pair_config_by_id.items(): @@ -508,7 +521,6 @@ def update_vault_pool_balances( pool.shares_by_account[recipient] += value return pool_balances - def get_block_balances( self, cached_data: Dict[int, Dict[ChecksumAddress, float]], blocks: List[int] ) -> Dict[int, Dict[ChecksumAddress, float]]: @@ -534,7 +546,9 @@ def get_block_balances( return new_block_data sorted_blocks = sorted(blocks) - cache_copy_of_account_bals: Dict[int, Dict[ChecksumAddress, float]] = deepcopy(cached_data) + cache_copy_of_account_bals: Dict[int, Dict[ChecksumAddress, float]] = deepcopy( + cached_data + ) for block in sorted_blocks: # find the closest prev block in the data @@ -558,22 +572,27 @@ def get_block_balances( # Fetch pair config at prev_block if not already done if self.pair_config_by_id is None: - self.pair_config_by_id = self.update_pair_config({}, to_block=prev_block) + self.pair_config_by_id = self.update_pair_config( + {}, to_block=prev_block + ) # Fetch Peg Stability term balances at prev_block if not already done if self.psm_balances_by_share_token is None: self.psm_balances_by_share_token = self.update_psm_pool_balances( - {}, to_block=prev_block) + {}, to_block=prev_block + ) # Fetch AMM Liquidity Pool term balances at prev_block if not already done if self.amm_balances_by_lp_token is None: self.amm_balances_by_lp_token = self.update_amm_pool_balances( - {}, to_block=prev_block) + {}, to_block=prev_block + ) # Fetch Vault term balances at prev_block if not already done if self.vault_balances_by_vault_share_token is None: - self.vault_balances_by_vault_share_token = self.update_vault_pool_balances( - {}, to_block=prev_block) + self.vault_balances_by_vault_share_token = ( + self.update_vault_pool_balances({}, to_block=prev_block) + ) # parse events since and update bals while start <= block: @@ -584,16 +603,18 @@ def get_block_balances( self.update_pair_config(self.pair_config_by_id, start, to_block) # Update PSM Pool term balances - self.update_psm_pool_balances(self.psm_balances_by_share_token, start, to_block) + self.update_psm_pool_balances( + self.psm_balances_by_share_token, start, to_block + ) # Update AMM Pool term balances - self.update_amm_pool_balances(self.amm_balances_by_lp_token, start, to_block) + self.update_amm_pool_balances( + self.amm_balances_by_lp_token, start, to_block + ) # Update Vault Liquidity Pool term balances self.update_vault_pool_balances( - self.vault_balances_by_vault_share_token, - start, - to_block + self.vault_balances_by_vault_share_token, start, to_block ) start = to_block + 1 @@ -615,7 +636,7 @@ def get_block_balances( amm_contract_function.fn_name, [ amm_pool.pair_config.amm_quote_token_addr, - amm_pool.term_config.share_token_addr + amm_pool.term_config.share_token_addr, ], ) for amm_pool in self.amm_balances_by_lp_token.values() @@ -625,7 +646,9 @@ def get_block_balances( # The results contain the following: # - The `result[0]` is the total balance of the asset token in the AMM pool # - The `result[1]` is the total balance of the share token in the AMM pool - for amm_pool, result in zip(self.amm_balances_by_lp_token.values(), multicall_results): + for amm_pool, result in zip( + self.amm_balances_by_lp_token.values(), multicall_results + ): # Update the total Ethena-asset and PSM-shares balance of each AMM pool amm_pool.total_assets = (result[0], result[1]) @@ -647,18 +670,29 @@ def get_block_balances( # If the account_addr is the vault_addr, then we need to attribute the # Ethena asset balances to the respective LV token holders if account_addr == amm_pool.pair_config.vault_addr: - vault_share_token_addr = amm_pool.pair_config.vault_share_token_addr - vault = self.vault_balances_by_vault_share_token[vault_share_token_addr] - for account_addr, account_shares in vault.shares_by_account.items(): + vault_share_token_addr = ( + amm_pool.pair_config.vault_share_token_addr + ) + vault = self.vault_balances_by_vault_share_token[ + vault_share_token_addr + ] + for ( + account_addr, + account_shares, + ) in vault.shares_by_account.items(): account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + account_bals[account_addr] = Decimal( + account_bals[account_addr] + ) + ( amount * Decimal(account_shares) / Decimal(vault.total_supply) ) else: account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + amount + account_bals[account_addr] = ( + Decimal(account_bals[account_addr]) + amount + ) # # Attribute PSM share token balances to their respective LP token holders # if amm_pool.term_config is not None: @@ -685,11 +719,20 @@ def get_block_balances( # If the account_addr is the Cork Vault address, then we need to attribute the # Ethena asset balances to the respective LV token holders if account_addr == psm_pool.pair_config.vault_addr: - vault_share_token_addr = psm_pool.pair_config.vault_share_token_addr - vault = self.vault_balances_by_vault_share_token[vault_share_token_addr] - for account_addr, account_shares in vault.shares_by_account.items(): + vault_share_token_addr = ( + psm_pool.pair_config.vault_share_token_addr + ) + vault = self.vault_balances_by_vault_share_token[ + vault_share_token_addr + ] + for ( + account_addr, + account_shares, + ) in vault.shares_by_account.items(): account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + account_bals[account_addr] = Decimal( + account_bals[account_addr] + ) + ( amount * Decimal(account_shares) / Decimal(vault.total_supply) @@ -699,6 +742,7 @@ def get_block_balances( elif account_addr == psm_pool.term_config.amm_pool_addr: lp_token_addr = psm_pool.term_config.amm_lp_token_addr amm_pool = self.amm_balances_by_lp_token[lp_token_addr] + # If there are other Uniswap V4 pools which manage PSM-shares, # the total amount of PSM-shares at the UniV4 PoolManager address # will exceed the amount present in the Cork AMM pool, @@ -709,16 +753,24 @@ def get_block_balances( * Decimal(amm_pool.total_assets[1]) / Decimal(psm_pool.total_supply) ) - for account_addr, account_shares in amm_pool.shares_by_account.items(): + + for ( + account_addr, + account_shares, + ) in amm_pool.shares_by_account.items(): account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + account_bals[account_addr] = Decimal( + account_bals[account_addr] + ) + ( amount * Decimal(account_shares) / Decimal(amm_pool.total_supply) ) else: account_bals.setdefault(account_addr, Decimal(0)) - account_bals[account_addr] = Decimal(account_bals[account_addr]) + amount + account_bals[account_addr] = ( + Decimal(account_bals[account_addr]) + amount + ) # Round off to 4 decimals for account_addr, account_bal in account_bals.items(): @@ -744,10 +796,11 @@ def get_block_balances( ) print("=" * 120) - print("Run without cached data", + print( + "Run without cached data", cork_integration.get_block_balances( - cached_data={}, blocks=[20000000, 20000001, 20000002] - ) + cached_data={}, blocks=[7686000, 7686001, 7686002] + ), ) # Example output: # { @@ -757,20 +810,21 @@ def get_block_balances( # } print("=" * 120, "\n" * 5) - print("Run with cached data", + print( + "Run with cached data", cork_integration.get_block_balances( cached_data={ - 20000000: { + 7686000: { Web3.to_checksum_address("0x123"): 100, Web3.to_checksum_address("0x456"): 200, }, - 20000001: { + 7686001: { Web3.to_checksum_address("0x123"): 101, Web3.to_checksum_address("0x456"): 201, }, }, - blocks=[20000002], - ) + blocks=[7686002], + ), ) print("=" * 120) # Example output: diff --git a/integrations/fluid_susde.py b/integrations/fluid_susde.py index 96c5ca5..0a5b795 100644 --- a/integrations/fluid_susde.py +++ b/integrations/fluid_susde.py @@ -5,12 +5,12 @@ from utils.fluid import vaultResolver_contract, vaultPositionResolver_contract from constants.fluid import sUSDe - +# covers all Fluid normal col SUSDE vaults. class FluidIntegration(Integration): def __init__(self): super().__init__( - IntegrationID.FLUID, + IntegrationID.FLUID_SUSDE, 21016131, Chain.ETHEREUM, [], @@ -28,7 +28,7 @@ def get_balance(self, user: str, block: int) -> float: vaultResolver_contract.functions.positionsByUser(user), block ) for i in range(len(userPositions)): - if vaultEntireDatas[i][3][8][0] == sUSDe: + if (vaultEntireDatas[i][3][8][0] == sUSDe) and not (vaultEntireDatas[i][1]): # not smart col types balance += userPositions[i][9] return balance / 1e18 except Exception as e: @@ -80,13 +80,10 @@ def get_relevant_vaults(self, block: int) -> list: ) relevantVaults = [] for vaultAddress in vaults: - supplyTokenOfVault = ( - call_with_retry( - vaultResolver_contract.functions.getVaultEntireData(vaultAddress), - block, - ) - )[3][8][0] - if supplyTokenOfVault == sUSDe: + vaultData = call_with_retry( + vaultResolver_contract.functions.getVaultEntireData(vaultAddress), block + ) + if (vaultData[3][8][0] == sUSDe) and not (vaultData[1]): # not smart col types relevantVaults.append(vaultAddress) self.blocknumber_to_susdeVaults[block] = relevantVaults return relevantVaults @@ -95,7 +92,7 @@ def get_relevant_vaults(self, block: int) -> list: if __name__ == "__main__": example_integration = FluidIntegration() print("getting relevant vaults") - print(example_integration.get_relevant_vaults(21088189)) + print(example_integration.get_relevant_vaults(21745303)) print("\n\n\ngetting participants") print(example_integration.get_participants(None)) diff --git a/integrations/fluid_susde_smart.py b/integrations/fluid_susde_smart.py new file mode 100644 index 0000000..8b89b4f --- /dev/null +++ b/integrations/fluid_susde_smart.py @@ -0,0 +1,162 @@ +from constants.chains import Chain +from integrations.integration_ids import IntegrationID +from integrations.integration import Integration +from utils.web3_utils import call_with_retry, W3_BY_CHAIN +from utils.fluid import ( + vaultResolver_contract, + vaultPositionResolver_contract, + dexResolver_contract, +) +from constants.fluid import sUSDe +import json + +# covers all Fluid smart col SUSDE vaults (LP positions) +class FluidIntegration(Integration): + + def __init__(self): + super().__init__( + IntegrationID.FLUID_SUSDE_SMART, + 21673938, + Chain.ETHEREUM, + [], + 30, + 1, + None, + None, + ) + self.blocknumber_to_susdeVaults = {} + + def get_balance(self, user: str, block: int) -> float: + balance = 0 + try: + userPositions, vaultEntireDatas = call_with_retry( + vaultResolver_contract.functions.positionsByUser(user), block + ) + dexEntireDatas = {} + for i in range(len(userPositions)): + if (vaultEntireDatas[i][3][8][0] == sUSDe or vaultEntireDatas[i][3][8][1] == sUSDe) and (vaultEntireDatas[i][1]): # ONLY smart col types + # underlying dex as supply token in the vault + # fetching the dex state to get the shares to tokens ratio + dexAddress = vaultEntireDatas[i][3][6] + if dexAddress not in dexEntireDatas: + dexEntireDatas[dexAddress] = ( + dexResolver_contract.functions.getDexEntireData(dexAddress).call( + block_identifier=block + ) + ) + + # Example: + # "totalSupplyShares": "16969369723826778595219443", + # "token0PerSupplyShare": "948923904934732323", + # "token1PerSupplyShare": "911761", + # assuming example user has 1% of total supply 169693697238267785952194: + # userPositionToken0 = 169693697238267785952194 * 948923904934732323 / 1e18 = 161_026.405826149269461177 SUSDE 161026405826149269461177 + # userPositionToken1 = 169693697238267785952194 * 911761 / 1e18 = 154_720.095087 USDT 154720095087 + + # add token1 position converted to SUSDE: + # e.g."pex.lastStoredPrice": "1151956348186710776274223104", + # 154720095087 * 1e54 / 1151956348186710776274223104 / 1e27 = 134310727425 SUSDE, adjust for decimals: + # whatever decimals token1 has, doing * token1NumeratorPrecision / token1DenominatorPrecision brings it to 1e12. + # 134310727425 * token1NumeratorPrecision / token1DenominatorPrecision * 1e6 = + # 134310727425 * 1e6 / 1 * 1e6 = 134310727425000000000000 + + # so total pos = 161026405826149269461177 + 134310727425000000000000 = 295_337.133251149269461177 SUSDE + + # userShares = userPositions[i][9] + # token0PerSupplyShare = dexEntireDatas[dexAddress][7][-4] + # token1PerSupplyShare = dexEntireDatas[dexAddress][7][-3] + userPositionToken0 = userPositions[i][9] * dexEntireDatas[dexAddress][7][-4] / 1e18 + userPositionToken1 = userPositions[i][9] * dexEntireDatas[dexAddress][7][-3] / 1e18 + + if (vaultEntireDatas[i][3][8][0] == sUSDe): + # token0 at the dex is sUSDE + balance += userPositionToken0 + # lastStoredPrice = dexEntireDatas[dexAddress][4][0] + userPositionToken1 = userPositionToken1 * 1e54 / dexEntireDatas[dexAddress][4][0] / 1e27; + # * 1e6 * numeratorPrecision / denominatorPrecision + userPositionToken1 = userPositionToken1 * 1e6 * dexEntireDatas[dexAddress][2][2] / dexEntireDatas[dexAddress][2][3] + + balance += userPositionToken1 + else: + # token1 at the dex is sUSDE + balance += userPositionToken1 + # lastStoredPrice = dexEntireDatas[dexAddress][4][0] + userPositionToken0 = userPositionToken0 * dexEntireDatas[dexAddress][4][0] / 1e27; + # * 1e6 * numeratorPrecision / denominatorPrecision + userPositionToken0 = userPositionToken0 * 1e6 * dexEntireDatas[dexAddress][2][0] / dexEntireDatas[dexAddress][2][1] + + balance += userPositionToken1 + return balance / 1e18 + except Exception as e: + return 0 + + def get_participants(self, blocks: list[int] | None) -> set[str]: + participants = [] + current_block = W3_BY_CHAIN[self.chain]["w3"].eth.get_block_number() + + relevant_vaults = self.get_relevant_vaults(current_block) + relavantUserPositions = [] + + try: + for vault in relevant_vaults: + relavantUserPositions += call_with_retry( + vaultPositionResolver_contract.functions.getAllVaultPositions( + vault + ), + current_block, + ) + for userPosition in relavantUserPositions: + owner = userPosition[1] + if owner not in participants: + participants.append(owner) + except Exception as e: + print(f"Error: {str(e)}") + return set(participants) + + def get_relevant_vaults(self, block: int) -> list: + if block in self.blocknumber_to_susdeVaults: + return self.blocknumber_to_susdeVaults[block] + + if self.blocknumber_to_susdeVaults != {}: + totalVaults = call_with_retry( + vaultResolver_contract.functions.getTotalVaults(), block + ) + for block_number in self.blocknumber_to_susdeVaults: + totalVaults_at_block = call_with_retry( + vaultResolver_contract.functions.getTotalVaults(), block_number + ) + if totalVaults == totalVaults_at_block: + self.blocknumber_to_susdeVaults[block] = ( + self.blocknumber_to_susdeVaults[block_number] + ) + return self.blocknumber_to_susdeVaults[block_number] + + vaults = call_with_retry( + vaultResolver_contract.functions.getAllVaultsAddresses(), block + ) + relevantVaults = [] + for vaultAddress in vaults: + vaultData = call_with_retry( + vaultResolver_contract.functions.getVaultEntireData(vaultAddress), block + ) + if (vaultData[3][8][0] == sUSDe or vaultData[3][8][1] == sUSDe) and (vaultData[1]): # ONLY smart col types + relevantVaults.append(vaultAddress) + self.blocknumber_to_susdeVaults[block] = relevantVaults + return relevantVaults + + +if __name__ == "__main__": + example_integration = FluidIntegration() + print("getting relevant vaults") + print(example_integration.get_relevant_vaults(21745303)) + + print("\n\n\ngetting participants") + print(example_integration.get_participants(None)) + + print("\n\n\n getting balance") + print( + example_integration.get_balance( + # should be ~1_117_500 SUSDE + "0xDB611d682cb1ad72fcBACd944a8a6e2606a6d158", 21745303 + ) + ) diff --git a/integrations/fluid_usde.py b/integrations/fluid_usde.py index 9ce5b8b..56aab32 100644 --- a/integrations/fluid_usde.py +++ b/integrations/fluid_usde.py @@ -5,12 +5,12 @@ from utils.fluid import vaultResolver_contract, vaultPositionResolver_contract from constants.fluid import USDe - +# covers all Fluid normal col USDE vaults. class FluidIntegration(Integration): def __init__(self): super().__init__( - IntegrationID.FLUID, + IntegrationID.FLUID_USDE, 21016131, Chain.ETHEREUM, [], @@ -28,10 +28,7 @@ def get_balance(self, user: str, block: int) -> float: vaultResolver_contract.functions.positionsByUser(user), block ) for i in range(len(userPositions)): - if ( - vaultEntireDatas[i][3][8][0] == USDe - or vaultEntireDatas[i][3][8][1] == USDe - ) and userPositions[i][10] != 40000: + if (vaultEntireDatas[i][3][8][0] == USDe) and not (vaultEntireDatas[i][1]): # not smart col types balance += userPositions[i][9] return balance / 1e18 except Exception as e: @@ -86,9 +83,7 @@ def get_relevant_vaults(self, block: int) -> list: vaultData = call_with_retry( vaultResolver_contract.functions.getVaultEntireData(vaultAddress), block ) - if (vaultData[3][8][0] == USDe or vaultData[3][8][1] == USDe) and not ( - vaultData[1] and vaultData[2] - ): + if (vaultData[3][8][0] == USDe) and not (vaultData[1]): # not smart col types relevantVaults.append(vaultAddress) self.blocknumber_to_usdeVaults[block] = relevantVaults return relevantVaults @@ -102,3 +97,10 @@ def get_relevant_vaults(self, block: int) -> list: print("\n\n\ngetting participants") print(example_integration.get_participants(None)) + + print("\n\n\n getting balance") + print( + example_integration.get_balance( + "0xD15B0aA03Bc9F74Aa3d07d078502867Da3B7d198", 21745303 + ) + ) diff --git a/integrations/fluid_usde_smart.py b/integrations/fluid_usde_smart.py new file mode 100644 index 0000000..bfa5237 --- /dev/null +++ b/integrations/fluid_usde_smart.py @@ -0,0 +1,147 @@ +from constants.chains import Chain +from integrations.integration_ids import IntegrationID +from integrations.integration import Integration +from utils.web3_utils import call_with_retry, W3_BY_CHAIN +from utils.fluid import ( + vaultResolver_contract, + vaultPositionResolver_contract, + dexResolver_contract, +) +from constants.fluid import USDe +import json + +# covers all Fluid smart col USDE vaults (LP positions) +class FluidIntegration(Integration): + + def __init__(self): + super().__init__( + IntegrationID.FLUID_USDE_SMART, + 21673938, + Chain.ETHEREUM, + [], + 30, + 1, + None, + None, + ) + self.blocknumber_to_usdeVaults = {} + + def get_balance(self, user: str, block: int) -> float: + balance = 0 + try: + userPositions, vaultEntireDatas = call_with_retry( + vaultResolver_contract.functions.positionsByUser(user), block + ) + dexEntireDatas = {} + for i in range(len(userPositions)): + if (vaultEntireDatas[i][3][8][0] == USDe or vaultEntireDatas[i][3][8][1] == USDe) and (vaultEntireDatas[i][1]): # ONLY smart col types + # underlying dex as supply token in the vault + # fetching the dex state to get the shares to tokens ratio + dexAddress = vaultEntireDatas[i][3][6] + if dexAddress not in dexEntireDatas: + dexEntireDatas[dexAddress] = ( + dexResolver_contract.functions.getDexEntireData(dexAddress).call( + block_identifier=block + ) + ) + + # For an example walkthrough see fluid_susde_smart.py + + # userShares = userPositions[i][9] + # token0PerSupplyShare = dexEntireDatas[dexAddress][7][-4] + # token1PerSupplyShare = dexEntireDatas[dexAddress][7][-3] + userPositionToken0 = userPositions[i][9] * dexEntireDatas[dexAddress][7][-4] / 1e18 + userPositionToken1 = userPositions[i][9] * dexEntireDatas[dexAddress][7][-3] / 1e18 + + if (vaultEntireDatas[i][3][8][0] == USDe): + # token0 at the dex is USDe + balance += userPositionToken0 + # lastStoredPrice = dexEntireDatas[dexAddress][4][0] + userPositionToken1 = userPositionToken1 * 1e54 / dexEntireDatas[dexAddress][4][0] / 1e27 + # * 1e6 * numeratorPrecision / denominatorPrecision + userPositionToken1 = userPositionToken1 * 1e6 * dexEntireDatas[dexAddress][2][2] / dexEntireDatas[dexAddress][2][3] + + balance += userPositionToken1 + else: + # token1 at the dex is USDe + balance += userPositionToken1 + # lastStoredPrice = dexEntireDatas[dexAddress][4][0] + userPositionToken0 = userPositionToken0 * dexEntireDatas[dexAddress][4][0] / 1e27 + # * 1e6 * numeratorPrecision / denominatorPrecision + userPositionToken0 = userPositionToken0 * 1e6 * dexEntireDatas[dexAddress][2][0] / dexEntireDatas[dexAddress][2][1] + + balance += userPositionToken1 + return balance / 1e18 + except Exception as e: + return 0 + + def get_participants(self, blocks: list[int] | None) -> set[str]: + participants = [] + current_block = W3_BY_CHAIN[self.chain]["w3"].eth.get_block_number() + + relevant_vaults = self.get_relevant_vaults(current_block) + relavantUserPositions = [] + + try: + for vault in relevant_vaults: + relavantUserPositions += call_with_retry( + vaultPositionResolver_contract.functions.getAllVaultPositions( + vault + ), + current_block, + ) + for userPosition in relavantUserPositions: + owner = userPosition[1] + if owner not in participants: + participants.append(owner) + except Exception as e: + print(f"Error: {str(e)}") + return set(participants) + + def get_relevant_vaults(self, block: int) -> list: + if block in self.blocknumber_to_usdeVaults: + return self.blocknumber_to_usdeVaults[block] + + if self.blocknumber_to_usdeVaults != {}: + totalVaults = call_with_retry( + vaultResolver_contract.functions.getTotalVaults(), block + ) + for block_number in self.blocknumber_to_usdeVaults: + totalVaults_at_block = call_with_retry( + vaultResolver_contract.functions.getTotalVaults(), block_number + ) + if totalVaults == totalVaults_at_block: + self.blocknumber_to_usdeVaults[block] = ( + self.blocknumber_to_usdeVaults[block_number] + ) + return self.blocknumber_to_usdeVaults[block_number] + + vaults = call_with_retry( + vaultResolver_contract.functions.getAllVaultsAddresses(), block + ) + relevantVaults = [] + for vaultAddress in vaults: + vaultData = call_with_retry( + vaultResolver_contract.functions.getVaultEntireData(vaultAddress), block + ) + if (vaultData[3][8][0] == USDe or vaultData[3][8][1] == USDe) and (vaultData[1]): # ONLY smart col types + relevantVaults.append(vaultAddress) + self.blocknumber_to_usdeVaults[block] = relevantVaults + return relevantVaults + + +if __name__ == "__main__": + example_integration = FluidIntegration() + print("getting relevant vaults") + print(example_integration.get_relevant_vaults(21745303)) + + print("\n\n\ngetting participants") + print(example_integration.get_participants(None)) + + print("\n\n\n getting balance") + print( + example_integration.get_balance( + # should be ~669k USDE + "0xDB611d682cb1ad72fcBACd944a8a6e2606a6d158", 21745303 + ) + ) diff --git a/integrations/fluid_usde_t4.py b/integrations/fluid_usde_t4.py deleted file mode 100644 index 07c67fa..0000000 --- a/integrations/fluid_usde_t4.py +++ /dev/null @@ -1,82 +0,0 @@ -from constants.chains import Chain -from integrations.integration_ids import IntegrationID -from integrations.integration import Integration -from utils.web3_utils import call_with_retry, W3_BY_CHAIN -from utils.fluid import ( - vaultResolver_contract, - vaultPositionResolver_contract, - dexResolver_contract, -) -from constants.fluid import USDe -import json - - -class FluidIntegration(Integration): - - def __init__(self): - super().__init__( - IntegrationID.FLUID, - 21016131, - Chain.ETHEREUM, - [], - 30, - 1, - None, - None, - ) - self.relevant_vaults = ["0x1B4EC865915872AEc7A30423fdA2584C9fa894C5"] - - def get_balance(self, user: str, block: int) -> float: - balance = 0 - try: - userPositions, vaultEntireDatas = call_with_retry( - vaultResolver_contract.functions.positionsByUser(user), block - ) - dexStates = {} - for i in range(len(userPositions)): - if ( - vaultEntireDatas[i][3][8][0] == USDe - or vaultEntireDatas[i][3][8][1] == USDe - ) and userPositions[i][10] == 40000: - # underlying dex as supply token in the vault - dexAddress = vaultEntireDatas[i][3][6] - if dexAddress not in dexStates: - dexStates[dexAddress] = ( - dexResolver_contract.functions.getDexState(dexAddress).call( - block_identifier=block - ) - ) - # fetching the dex state to get the shares to tokens ratio - token0PerSupplyShare = dexStates[dexAddress][-4] - balance += userPositions[i][9] * token0PerSupplyShare - return balance / 1e18 - except Exception as e: - return 0 - - def get_participants(self, blocks: list[int] | None) -> set[str]: - participants = [] - current_block = W3_BY_CHAIN[self.chain]["w3"].eth.get_block_number() - relavantUserPositions = [] - - try: - for vault in self.relevant_vaults: - relavantUserPositions += call_with_retry( - vaultPositionResolver_contract.functions.getAllVaultPositions( - vault - ), - current_block, - ) - for userPosition in relavantUserPositions: - owner = userPosition[1] - if owner not in participants: - participants.append(owner) - except Exception as e: - print(f"Error: {str(e)}") - return set(participants) - - -if __name__ == "__main__": - example_integration = FluidIntegration() - current_block = W3_BY_CHAIN[example_integration.chain]["w3"].eth.get_block_number() - print("\n\n\ngetting participants") - print(example_integration.get_participants(None)) diff --git a/integrations/integration.py b/integrations/integration.py index 1550351..e141766 100644 --- a/integrations/integration.py +++ b/integrations/integration.py @@ -1,10 +1,10 @@ -from abc import ABC, abstractmethod +from abc import ABC from typing import Dict, List, Optional, Set from eth_typing import ChecksumAddress from constants.chains import Chain -from integrations.integration_ids import IntegrationID from constants.summary_columns import SummaryColumn +from integrations.integration_ids import IntegrationID class Integration(ABC): diff --git a/integrations/integration_ids.py b/integrations/integration_ids.py index 9661639..623b4e0 100644 --- a/integrations/integration_ids.py +++ b/integrations/integration_ids.py @@ -393,7 +393,10 @@ class IntegrationID(Enum): ) # Fluid - FLUID = ("Fluid_susde", "Fluid sUSDe", Token.SUSDE) + FLUID_SUSDE = ("Fluid_susde", "Fluid sUSDe", Token.SUSDE) + FLUID_USDE = ("Fluid_usde", "Fluid USDe", Token.USDE) + FLUID_SUSDE_SMART = ("Fluid_susde_smart", "Fluid sUSDe Smart", Token.SUSDE) + FLUID_USDE_SMART = ("Fluid_usde_smart", "Fluid USDe Smart", Token.USDE) # Cork CORK_USDE = ("cork_usde", "Cork USDe", Token.USDE) @@ -436,6 +439,13 @@ class IntegrationID(Enum): Token.USDE, ) + # Rumpel + RUMPEL_SENA_LP = ( + "rumpel_kpsats3_sena_lp_held", + "Rumpel kpSATS-3/sENA LP", + Token.SENA + ) + # InfinityPools INFINITYPOOLS = ( "infinityPools", diff --git a/integrations/rumpel_integration.py b/integrations/rumpel_integration.py new file mode 100644 index 0000000..a6ce8c2 --- /dev/null +++ b/integrations/rumpel_integration.py @@ -0,0 +1,144 @@ +from typing import Callable, Dict, List, Optional, Set +from constants.chains import Chain +from constants.summary_columns import SummaryColumn +from integrations.cached_balances_integration import CachedBalancesIntegration +from integrations.integration_ids import IntegrationID +from web3 import Web3 +from eth_typing import ChecksumAddress + +import math +from utils.web3_utils import w3, fetch_events_logs_with_retry, fetch_transaction_receipt_with_retry, call_with_retry +from constants.rumpel import KPSATS3_SENA_UNIV3_POOL_DEPLOYED_BLOCK, KPSATS3_SENA_UNIV3_POOL_CONTRACT, UNIV3_NONFUNGIBLE_POSITION_MANAGER_CONTRACT, UNIV3_NONFUNGUBLE_POSITION_MANAGER_ADDRESS, KPSAT3_ADDRESS, SENA_ADDRESS + +def calculate_lp_tokens(tick, tick_lower, tick_upper, sqrt_price, liquidity): + if liquidity == 0: + return [0, 0] + # if abs(tick_lower) > MAX_TICK_RANGE or abs(tick_upper) > MAX_TICK_RANGE: + # return [0, 0] + t0 = 0 + t1 = 0 + sqrt_ratio_u = math.sqrt(1.0001**tick_upper) + sqrt_ratio_l = math.sqrt(1.0001**tick_lower) + if tick_lower < tick < tick_upper: + t0 = liquidity * (sqrt_ratio_u - sqrt_price) / (sqrt_price * sqrt_ratio_u) + t1 = liquidity * (sqrt_price - sqrt_ratio_l) + elif tick >= tick_upper: + t1 = liquidity * (sqrt_ratio_u - sqrt_ratio_l) + else: + t0 = liquidity * (sqrt_ratio_u - sqrt_ratio_l) / (sqrt_ratio_u * sqrt_ratio_l) + return [abs(t0 / 10**18), abs(t1 / 10**18)] + +class RumpelIntegration( + CachedBalancesIntegration +): + def __init__( + self, + integration_id: IntegrationID, + start_block: int, + chain: Chain = Chain.ETHEREUM, + summary_cols: Optional[List[SummaryColumn]] = None, + reward_multiplier: int = 1, + balance_multiplier: int = 1, + excluded_addresses: Optional[Set[ChecksumAddress]] = None, + end_block: Optional[int] = None, + ethereal_multiplier: int = 0, + ethereal_multiplier_func: Optional[Callable[[int, str], int]] = None, + ): + super().__init__( + integration_id, + start_block, + chain, + summary_cols, + reward_multiplier, + balance_multiplier, + excluded_addresses, + end_block, + ethereal_multiplier, + ethereal_multiplier_func, + ) + + def get_block_balances( + self, cached_data: Dict[int, Dict[ChecksumAddress, float]], blocks: List[int] + ) -> Dict[int, Dict[ChecksumAddress, float]]: + users: Dict[int, Dict[ChecksumAddress, float]] = {} + + for block in blocks: + start = KPSATS3_SENA_UNIV3_POOL_DEPLOYED_BLOCK + end = block + users[end] = {} + + pool = KPSATS3_SENA_UNIV3_POOL_CONTRACT + nfpm = UNIV3_NONFUNGIBLE_POSITION_MANAGER_CONTRACT + + poolState = call_with_retry(pool.functions.slot0(), block=end) + pool_price_sqrt_x96 = poolState[0] + pool_tick = poolState[1] + + # query all mint events directly from the pool + mints = fetch_events_logs_with_retry( + "Rumpel liquidity Minted", + pool.events.Mint(), + start, + end, + ) + + lp_positions = set() + increase_liquidity_event = UNIV3_NONFUNGIBLE_POSITION_MANAGER_CONTRACT.events.IncreaseLiquidity() + increase_liquidity_param_types = [param['type'] for param in increase_liquidity_event.abi['inputs']] + increase_liquidity_signature = f"{increase_liquidity_event.event_name}({','.join(increase_liquidity_param_types)})" + topic_hash = w3.keccak(text=increase_liquidity_signature).hex() + + # get the IncreaseLiquidity event from the NonFungiblePositionManager Increase Liquidity Event + # dev: if the tx updates multiple pools, this will include out of scope positions. + # for this reason there is a check on the positions tokens + for mint in mints: + tx = fetch_transaction_receipt_with_retry(Chain.ETHEREUM, mint["transactionHash"].hex()); + for log in tx.logs: + if(log.topics[0].hex() == topic_hash): + token_id = int.from_bytes(log.topics[1], 'big') + lp_positions.add(token_id); + + + # get each positions owner and data, calculate the users balance, and update storage + for lp_position in lp_positions: + owner = call_with_retry(nfpm.functions.ownerOf(lp_position), block=end) + position_data = call_with_retry(nfpm.functions.positions(lp_position), block=end) + + # check the position is for this pool + token0 = position_data[2] + token1 = position_data[3] + if(token0 != KPSAT3_ADDRESS or token1 != SENA_ADDRESS): + continue + + lower_tick = position_data[5] + upper_tick = position_data[6] + liquidity = position_data[7] + + balances = calculate_lp_tokens(pool_tick, lower_tick, upper_tick, pool_price_sqrt_x96 / (2**96), liquidity) + + if users.get(end, {}).get(owner) is not None: + users[end][owner] += balances[1] + else: + users[end][owner] = balances[1] + + return users + + + +if __name__ == "__main__": + example_integration = RumpelIntegration( + integration_id=IntegrationID.RUMPEL_SENA_LP, + start_block=KPSATS3_SENA_UNIV3_POOL_DEPLOYED_BLOCK, + summary_cols=[SummaryColumn.TEMPLATE_PTS], + chain=Chain.ETHEREUM, + reward_multiplier=20, + excluded_addresses={ + Web3.to_checksum_address("0x0000000000000000000000000000000000000000") + }, + end_block=40000000, + ) + print( + example_integration.get_block_balances( + cached_data={}, blocks=[21645700, 21645701, 21645702] + ) + ) \ No newline at end of file diff --git a/integrations/zerolend_integration.py b/integrations/zerolend_integration.py index 8fea4b8..899e264 100644 --- a/integrations/zerolend_integration.py +++ b/integrations/zerolend_integration.py @@ -1,10 +1,8 @@ -from typing import Callable, Dict, List, Optional, Set +import requests from constants.chains import Chain from utils.web3_utils import w3 -from constants.summary_columns import SummaryColumn from integrations.cached_balances_integration import CachedBalancesIntegration from integrations.integration_ids import IntegrationID -import requests ZEOLEND_API_URL = "https://api.zerolend.xyz" @@ -17,23 +15,23 @@ def __init__( token: str, ): super().__init__( - integration_id, - 20000000, # not used - Chain.ETHEREUM, - None, - reward_multiplier, - 1, - None, - None, - None, + integration_id=integration_id, + start_block=20000000, # not used + chain=Chain.ETHEREUM, + summary_cols=None, + reward_multiplier=reward_multiplier, + balance_multiplier=1, + excluded_addresses=None, + end_block=None, ) self.token = token + def get_balance(self, user: str, block: int) -> float: try: - url = f"{ZEOLEND_API_URL}/ethena" # TODO: add api url + url = f"{ZEOLEND_API_URL}/ethena" params = {"token": self.token, "address": str(user), "blockNo": str(block)} - response = requests.get(url, params=params) # type: ignore + response = requests.get(url, params=params, timeout=10) # type: ignore print(response.json()) data = response.json() asset_balance = data["data"] @@ -49,7 +47,7 @@ def get_participants(self, blocks: list[int] | None) -> set[str]: """ url = f"{ZEOLEND_API_URL}/ethena/participants" params = {"token": self.token} - response = requests.get(url, params=params) + response = requests.get(url, params=params, timeout=10) data = response.json() return data["data"] diff --git a/utils/web3_utils.py b/utils/web3_utils.py index 107fb48..e4b9e7a 100644 --- a/utils/web3_utils.py +++ b/utils/web3_utils.py @@ -1,12 +1,12 @@ import logging import os import time +from datetime import datetime import traceback from typing import Iterable from dotenv import load_dotenv from eth_abi.abi import decode -from datetime import datetime from web3 import Web3 from web3.types import BlockIdentifier, EventData @@ -104,7 +104,11 @@ MULTICALL_ADDRESS = ( "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696" # Ethereum mainnet address ) -MULTICALL_ADDRESS_BY_CHAIN = {Chain.SWELL: "0xcA11bde05977b3631167028862bE2a173976CA11"} +MULTICALL_ADDRESS_BY_CHAIN = { + Chain.SWELL: "0xcA11bde05977b3631167028862bE2a173976CA11", + Chain.SEPOLIA: "0x25Eef291876194AeFAd0D60Dff89e268b90754Bb", + Chain.ETHEREUM: MULTICALL_ADDRESS, +} def fetch_events_logs_with_retry( @@ -114,6 +118,7 @@ def fetch_events_logs_with_retry( to_block: int | str = "latest", retries: int = 3, delay: int = 2, + # pylint: disable=redefined-builtin filter: dict | None = None, ) -> Iterable[EventData]: for attempt in range(retries): @@ -121,7 +126,10 @@ def fetch_events_logs_with_retry( if filter is None: return contract_event.get_logs(fromBlock=from_block, toBlock=to_block) else: - return contract_event.get_logs(fromBlock=from_block, toBlock=to_block, argument_filters=filter) + return contract_event.get_logs( + fromBlock=from_block, toBlock=to_block, argument_filters=filter + ) + except Exception as e: if attempt < retries - 1: time.sleep(delay) @@ -149,6 +157,7 @@ def call_with_retry(contract_function, block="latest", retries=3, delay=2): raise e +# pylint: disable=redefined-outer-name def multicall(w3: Web3, calls: list, block_identifier: BlockIdentifier = "latest"): multicall_contract = w3.eth.contract( address=Web3.to_checksum_address(MULTICALL_ADDRESS), abi=MULTICALL_ABI @@ -175,6 +184,7 @@ def multicall(w3: Web3, calls: list, block_identifier: BlockIdentifier = "latest def multicall_by_address( + # pylint: disable=redefined-outer-name w3: Web3, multical_address: str, calls: list, @@ -214,3 +224,21 @@ def get_block_date(block: int, chain: Chain, adjustment: int = 0) -> str: ) timestamp_date = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H") return timestamp_date + + +def fetch_transaction_receipt_with_retry( + chain: Chain, transaction_hash, retries=3, delay=2 +): + wb3 = W3_BY_CHAIN[chain]["w3"] + for attempt in range(retries): + try: + return wb3.eth.get_transaction_receipt(transaction_hash) + except Exception as e: + if attempt < retries - 1: + time.sleep(delay) + continue + else: + msg = f"Error fetching transaction: {e}, {traceback.format_exc()}" + logging.error(msg) + slack_message(msg) + raise e