{"openapi":"3.1.0","info":{"title":"Morpho over Airnode","version":"1.1.0","description":"Operator-run read-only integration of Morpho indexed V1/V2 vaults, adapters and markets. Fixed GraphQL queries preserve nulls and indexed timestamps. A query Morpho answers with GraphQL errors is a 502 rather than a signed answer, so a signed answer is a successful one. Not a Morpho-operated or endorsed signer; no confidential positions, safety scores or execution. Documentation: https://docs.morpho.org/tools/offchain/api/get-started/\n\nEvery response carries an EIP-191 signature from the Airnode that served it, so the data can be attributed back to its source rather than trusted on reputation."},"servers":[{"url":"https://airnode-morpho.fly.dev"}],"paths":{"/":{"get":{"operationId":"getDocumentation","summary":"This document.","description":"Describes the operations this Airnode serves and how to verify what it returns. Free.","responses":{"200":{"description":"An OpenAPI document describing this Airnode.","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"operationId":"performRequest","summary":"Perform an operation and attest to the response.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"title":"listVaults","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"listVaults","description":"Find Morpho V1 vaults by chain and exact underlying asset.","x-airnode-returns":{"vaults":"object"}},"parameters":{"type":"object","additionalProperties":false,"required":["variables"],"properties":{"variables":{"type":"object","description":"Variables for this fixed, read-only query. No caller-supplied query, endpoint or pagination size.","required":["chainId","assetAddress"],"properties":{"chainId":{"type":"integer","minimum":1,"description":"Exact EVM chain ID; never inferred from an address."},"assetAddress":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"Exact contract address on the specified chain."},"skip":{"type":"integer","minimum":0,"default":0,"description":"Offset; advance by pageInfo.count. Dynamic index, not a consistent historical snapshot."}}}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data then carries those names instead of the whole body, and the projection is part of the signed request identity. A pointer the response does not have is a 400."}}},{"title":"getVault","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"getVault","description":"Read a Morpho V1 vault and its market allocations.","x-airnode-returns":{"vaultByAddress":"object"}},"parameters":{"type":"object","additionalProperties":false,"required":["variables"],"properties":{"variables":{"type":"object","description":"Variables for this fixed, read-only query. No caller-supplied query, endpoint or pagination size.","required":["chainId","address"],"properties":{"chainId":{"type":"integer","minimum":1,"description":"Exact EVM chain ID; never inferred from an address."},"address":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"Exact contract address on the specified chain."}}}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data then carries those names instead of the whole body, and the projection is part of the signed request identity. A pointer the response does not have is a 400."}}},{"title":"getMarket","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"getMarket","description":"Read a Morpho market, oracle address and indexed lending state.","x-airnode-returns":{"marketById":"object"}},"parameters":{"type":"object","additionalProperties":false,"required":["variables"],"properties":{"variables":{"type":"object","description":"Variables for this fixed, read-only query. No caller-supplied query, endpoint or pagination size.","required":["chainId","marketId"],"properties":{"chainId":{"type":"integer","minimum":1,"description":"Exact EVM chain ID; never inferred from an address."},"marketId":{"type":"string","pattern":"^0x[0-9a-fA-F]{64}$","description":"Exact bytes32 market ID returned by a vault allocation, not a token address."}}}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data then carries those names instead of the whole body, and the projection is part of the signed request identity. A pointer the response does not have is a 400."}}},{"title":"listVaultsV2","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"listVaultsV2","description":"Find V2 vaults and fee wrappers by chain and underlying asset.","x-airnode-returns":{"vaultV2s":"object"}},"parameters":{"type":"object","additionalProperties":false,"required":["variables"],"properties":{"variables":{"type":"object","required":["chainId","assetAddress"],"properties":{"chainId":{"type":"integer","minimum":1,"description":"Exact EVM chain ID; never inferred from an address."},"assetAddress":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"Exact contract address on the specified chain."},"skip":{"type":"integer","minimum":0,"default":0,"description":"Offset within this collection. Fixed page size ten. Follow pageInfo and retain chain/address identity across pages."}}}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data then carries those names instead of the whole body, and the projection is part of the signed request identity. A pointer the response does not have is a 400."}}},{"title":"getVaultV2","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"getVaultV2","description":"Read a V2 vault or fee wrapper, fees, liquidity and access gates.","x-airnode-returns":{"vaultV2ByAddress":"object"}},"parameters":{"type":"object","additionalProperties":false,"required":["variables"],"properties":{"variables":{"type":"object","required":["chainId","address"],"properties":{"chainId":{"type":"integer","minimum":1,"description":"Exact EVM chain ID; never inferred from an address."},"address":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"Exact contract address on the specified chain."}}}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data then carries those names instead of the whole body, and the projection is part of the signed request identity. A pointer the response does not have is a 400."}}},{"title":"listVaultV2Adapters","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"listVaultV2Adapters","description":"Page a V2 vault's adapters and their nested-vault targets.","x-airnode-returns":{"vaultV2ByAddress":"object"}},"parameters":{"type":"object","additionalProperties":false,"required":["variables"],"properties":{"variables":{"type":"object","required":["chainId","address"],"properties":{"chainId":{"type":"integer","minimum":1,"description":"Exact EVM chain ID; never inferred from an address."},"address":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"Exact contract address on the specified chain."},"skip":{"type":"integer","minimum":0,"default":0,"description":"Offset within this collection. Fixed page size ten. Follow pageInfo and retain chain/address identity across pages."}}}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data then carries those names instead of the whole body, and the projection is part of the signed request identity. A pointer the response does not have is a 400."}}},{"title":"listAdapterMarkets","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"listAdapterMarkets","description":"Page indexed market positions for an adapter address.","x-airnode-returns":{"marketPositions":"object"}},"parameters":{"type":"object","additionalProperties":false,"required":["variables"],"properties":{"variables":{"type":"object","required":["chainId","adapterAddress"],"properties":{"chainId":{"type":"integer","minimum":1,"description":"Exact EVM chain ID; never inferred from an address."},"adapterAddress":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"Exact contract address on the specified chain."},"skip":{"type":"integer","minimum":0,"default":0,"description":"Offset within this collection. Fixed page size ten. Follow pageInfo and retain chain/address identity across pages."}}}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data then carries those names instead of the whole body, and the projection is part of the signed request identity. A pointer the response does not have is a 400."}}}]}}}},"responses":{"200":{"description":"The upstream response and the attestation over it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attestation"}}}},"400":{"description":"The operation exists but the parameters or the projection are missing, unknown or malformed, or the projection names a path the response does not have."},"404":{"description":"No such operation."},"502":{"description":"The upstream API could not be reached or refused the request."}}}}},"components":{"schemas":{"Attestation":{"type":"object","required":["airnode","requestHash","timestamp","data","signature"],"properties":{"airnode":{"type":"string","description":"The address whose signature attests to this response."},"requestHash":{"type":"string","description":"Identifies the request this response answers."},"timestamp":{"type":"string","description":"Unix seconds at which the response was signed."},"data":{"description":"The upstream response body: the JSON value when it parses as JSON, the text as received otherwise. With a projection, an object keyed by the names it asked for."},"signature":{"type":"string","description":"The attestation itself."}}}}},"x-airnode":{"address":"0xD96CDFCf34c9951564Ca227A697658D61dd8B042","version":"0.1.2","attestation":{"scheme":"eip191-secp256k1","digest":"keccak256(abi.encodePacked(bytes32 requestHash, uint256 timestamp, bytes dataBytes))","dataBytes":"UTF-8 of data, or of its compact JSON when data is not a string","requestHash":"keccak256(utf8(JSON.stringify([operation, parameterEntriesSortedByName]))), where an object value is itself entries sorted by name, at any depth, and an array keeps its order. A request that carries a projection appends its entries, sorted the same way, as a third element","verify":"Recover the signer of the signature over the digest and check it equals the address above."},"passthrough":{"url":"https://airnode-morpho.fly.dev/api","routes":{"listVaults":{"method":"POST","path":"/graphql","body":{"query":"query($chainId:Int!,$assetAddress:String!,$skip:Int!=0){vaults(first:10,skip:$skip,where:{chainId_in:[$chainId],assetAddress_in:[$assetAddress]}){items{address name symbol listed chain { id } asset { address symbol decimals }}pageInfo{count countTotal limit skip}}}"},"parameters":{"variables":"body"}},"getVault":{"method":"POST","path":"/graphql","body":{"query":"query($chainId:Int!,$address:String!){vaultByAddress(chainId:$chainId,address:$address){address name symbol listed chain { id } asset { address symbol decimals } liquidity { underlying usd } state { blockNumber timestamp totalAssets totalAssetsUsd totalSupply fee owner curator guardian timelock apy netApy netApyExcludingRewards allRewards { asset { address symbol decimals } supplyApr } allocation { blockNumber supplyAssets supplyAssetsUsd supplyCap market { marketId chain { id } listed loanAsset { address symbol decimals } collateralAsset { address symbol decimals } oracle { address type } irmAddress lltv state { blockNumber timestamp supplyAssets borrowAssets supplyAssetsUsd borrowAssetsUsd liquidityAssets liquidityAssetsUsd utilization supplyApy borrowApy netSupplyApy netBorrowApy fee } } } }}}"},"parameters":{"variables":"body"}},"getMarket":{"method":"POST","path":"/graphql","body":{"query":"query($chainId:Int!,$marketId:String!){marketById(chainId:$chainId,marketId:$marketId){marketId chain { id } listed loanAsset { address symbol decimals } collateralAsset { address symbol decimals } oracle { address type } irmAddress lltv state { blockNumber timestamp supplyAssets borrowAssets supplyAssetsUsd borrowAssetsUsd liquidityAssets liquidityAssetsUsd utilization supplyApy borrowApy netSupplyApy netBorrowApy fee }}}"},"parameters":{"variables":"body"}},"listVaultsV2":{"method":"POST","path":"/graphql","body":{"query":"query($chainId:Int!,$assetAddress:Address!,$skip:Int!=0){vaultV2s(first:10,skip:$skip,where:{chainId_in:[$chainId],assetAddress_in:[$assetAddress]}){items{address name symbol listed type chain{id} asset{address symbol decimals}}pageInfo{count countTotal limit skip}}}"},"parameters":{"variables":"body"}},"getVaultV2":{"method":"POST","path":"/graphql","body":{"query":"query($chainId:Int!,$address:String!){vaultV2ByAddress(chainId:$chainId,address:$address){address name symbol listed type chain{id} asset{address symbol decimals} owner{address} curator{address} totalAssets totalSupply totalAssetsUsd idleAssets idleAssetsUsd liquidity liquidityUsd performanceFee managementFee apy netApy netApyExcludingRewards avgNetApy(lookback:SIX_HOURS) avgNetApyExcludingRewards(lookback:SIX_HOURS) rewards{asset{address symbol decimals} supplyApr} liquidityAdapter{address type} gatesConfig{sendSharesGate{address abdicated pendingAddress pendingExecutableAt pendingAbdicationExecutableAt} receiveAssetsGate{address abdicated pendingAddress pendingExecutableAt pendingAbdicationExecutableAt} receiveSharesGate{address abdicated pendingAddress pendingExecutableAt pendingAbdicationExecutableAt} sendAssetsGate{address abdicated pendingAddress pendingExecutableAt pendingAbdicationExecutableAt}} warnings{type level}}}"},"parameters":{"variables":"body"}},"listVaultV2Adapters":{"method":"POST","path":"/graphql","body":{"query":"query($chainId:Int!,$address:String!,$skip:Int!=0){vaultV2ByAddress(chainId:$chainId,address:$address){address chain{id} adapters(first:10,skip:$skip){items{__typename address chain{id} type assets assetsUsd forceDeallocatePenalty vault{address chain{id}} ...on MetaMorphoAdapter{metaMorpho{address chain{id}}} ...on MorphoVaultV2Adapter{innerVault{address chain{id}}}}pageInfo{count countTotal limit skip}}}}"},"parameters":{"variables":"body"}},"listAdapterMarkets":{"method":"POST","path":"/graphql","body":{"query":"query($chainId:Int!,$adapterAddress:String!,$skip:Int!=0){marketPositions(first:10,skip:$skip,where:{chainId_in:[$chainId],userAddress_in:[$adapterAddress]}){items{id user{address chain{id}} market{marketId chain { id } listed loanAsset { address symbol decimals } collateralAsset { address symbol decimals } oracle { address type } irmAddress lltv state { blockNumber timestamp supplyAssets borrowAssets supplyAssetsUsd borrowAssetsUsd liquidityAssets liquidityAssetsUsd utilization supplyApy borrowApy netSupplyApy netBorrowApy fee }} state{timestamp supplyAssets supplyAssetsUsd supplyShares borrowAssets borrowShares collateral}}pageInfo{count countTotal limit skip}}}"},"parameters":{"variables":"body"}}},"parameters":{"x-airnode-sign":"false skips the signature: no attestation headers, and the body streamed through with no size cap.","x-airnode-project":"alias:pointer, once per field to keep, each pointer an RFC 6901 JSON Pointer into the upstream response. The body becomes an object of those aliases, the signature covers it, and a pointer the response does not have is a 400."},"flow":"Send the request the provider documents, method, path, query and body as they are, to url followed by the route's path. The body comes back as the upstream sent it, with the upstream's status, and on success the attestation is in the X-Airnode-Address, X-Airnode-Request-Hash, X-Airnode-Timestamp and X-Airnode-Signature headers, with the operation served in X-Airnode-Operation. Every query parameter starting with x-airnode- is this Airnode's own, listed under parameters, and never reaches the upstream.","requestHash":"keccak256(utf8(JSON.stringify([\"passthrough\", method, path, queryEntriesSortedByName, body]))), where method is upper case, path and query are what was sent after url with the Airnode's own parameters removed, and body is the request body as sent, or an empty string. A request that carries a projection appends its entries, sorted by name, as a sixth element","dataBytes":"UTF-8 of the response body exactly as received, or of the projected object as compact JSON"}}}