Developers Docs
Search
⌃K

Common Subgraph Schema

We have created a graphql schema that is common to all DeFi protocols and captures all the data needed for SimpleFi to make ROI calculations. You can either create a specific subgraph fro yothat makes it easier to develop subgraph implementations.

Entities Description

Terminology

Market

Market is used to describe any smart contract that allows users to invest in it or borrow from it. For example Uniswap V2 is a protocol and all the pair contracts (liquidity pools) of Uniswap V2 are Markets of this protocol. Every pair contract is an individual market. For example a DAI-ETH pair is a market where users can provide liquidity by depositing DAI and ETH thus investing in this market.

Position

Position is used to describe any investment made, or debt taken, by a user in a market. For example a user can deposit DAI in a Compound DAI market thus creating a Position of type INVESTMENT in the DAI market of the Compound protocol. Similarly a user can borrow USDT from an Aave market thus creating a Position of type DEBT in this market of the Aave protocol.

Enums

We have defined some enums to store common values:

Enum Blockchain

This is used with every entity so that we can distinguish subgraphs for the same protocol on different blockchains (e.g. Curve on Ethereum mainnet and Polygon).

Enum TokenStandard

This is used to define the Token standard of a Token entity. This is useful when we want to select the correct ABI to interact with the token contract.

Enum ProtocolName

This is used in the Market entity to store a UI-friendly name of the protocol.

Enum ProtocolType

This is used in the Market entity. It can be useful when some logic needs to be implemented based on the type of protocol while processing this data. This is not an exhaustive list, it will keep growing as we integrate more protocols.
  • STAKING : Markets where users stake their tokens for governance
  • LENDING : Markets that allow users to borrow assets
  • EXCHANGE : Markets that allow users to exchange one asset to another
  • INSURANCE : Markets that provide insurance on asset prices
  • STABLECOIN : Markets that mint stable coins on depositing some collateral
  • DERIVATIVE : Markets allowing users to trade derivative of a base asset
  • SYNTHETIC_TOKEN : Markets which are digital representation of physical asset
  • TOKEN_MANAGEMENT : Automated strategy driven markets that further invest pooled assets in other markets
  • PREDICTION_MARKET : Markets allowing users to place bets
  • LP_FARMING : Markets where users deposit their LP token from another market to get extra rewards
Sometimes a market may look like supporting multiple functionality in which case we decide ProtocolType based on properties of the LP token minted by the market.
For example mAssets (mUSD and mBTC) are treated as STABLECOIN even though the mAsset contract itself allows exchange of other stable coins deposited into it. mAssets are LP tokens that don't change their price with change in market reserves. All fee or interest generated through swapping fee or further lending of assets is redirected to another market called imAsset (Interest generating mAsset). Therefore we consider it a STABLECOIN instead of EXCHANGE protocol because in EXCHANGE protocol LP token holders earn part of the swap fee.

Enum PositionType

This is used in the Position entity. Possible values are:
  • INVESTMENT : When a user invests funds in a market
  • DEBT : When a user borrows funds from a market
Please note that withdrawal from a market can be one of the following:
  • Redemption of full/part of the investment that was made by the user earlier in the market
  • Borrow funds from the market
In case it's a redemption we update the existing Position with type INVESTMENT. If it's borrowing funds then we create a new DEBT position if the user is borrowing for the first time from the market or update existing Positions with type DEBT

Enum TransactionType

This is used in the Transaction entity. Whenever we update a position we store current values of the position in the PositionSnapshot entity. In this PositionSnapshot entity we store a Transaction entity which stores information of the interaction by user that changes the position. Possible values for TransactionType are:
  • INVEST : When a user invests funds in a market
  • REDEEM : when a user redeems his earlier investments from a market
  • BORROW : When a user borrows funds from a market
  • REPAY : When a user repays part of the due amount to the market
  • TRANSFER_IN : When a someone transfers a positional token (for example LP token) to current user
  • TRANSFER_OUT : When a current user transfers a positional token (for example LP token) to someone

Entities

Account

This entity is used to store all addresses as accounts. We can't differentiate between a smart contract address and an externally owned address in a subgraph's mapping code therefore we are ignoring it.

Token

This entity is used to store all ERC20, ERC721, ERC1155 tokens with their useful propeties like:
  • id : Contract address of the token
  • tokenStandard : One of values from TokenStandard enum
  • name : Name of the token if available in contract
  • symbol : Symbol of the token that is used in UI of exchanges and other application using this token
  • decimals : Number of decimals of the token
  • mintedByMarket : It is null by default. If the token is minted by a market as LP token then we store the market id here. It can used to connect nested positions.
  • blockNumber : Block number at which we created this entity in the index. Please note that this is not always equal to the block number in which the token contract was deployed
  • timestamp: Timestamp of the block with above block number

TokenBalance

This is not an entity but a specific string format that we use to store balance of a user of a token.
tokenAddress|accountAddress|tokenAmountBigInt

Market

As described above this entity is used to store smart contracts that allow users to invest in or borrow from it. It has the following properties:
  • id : Contract address of the market, it can be a mix of contract address and one ID in case a single smart contract is used to keep track of multiple markets (like master chef farms)
  • account : Id of the Account entity for this smart contract. This can be used to fetch positions of this market in other markets (for example, the position that a yield farm holds in a Uniswap liquidity pool)
  • protocolName : One of the values of ProtocolName enum
  • protocolType : One of the values of ProtocolType enum
  • inputTokens : List of tokens that can be deposited in this market as investment
  • outputToken : Token that is minted by the market to track the position of a user in the market (e.g. an LP token)
  • rewardTokens : Some markets, such as yield farming contracts, provide some tokens as rewards to investors as an additional benefit. This property is used to store the list of all such tokens
  • inputTokenBalances : Total balance of this market contract of all input tokens in TokenBalance format
  • outputTokenTotalSupply : Total supply of output token
  • blockNumber : Block number of the block in which this market smart contract was deployed
  • timestamp : Timestamp of the above block number
  • positions: List of positions taken by all the users in this market
  • history: List of snapshots for this market

MarketSnapshot

This entity is used to store values of Market entity before every update to it. It has following properties:
  • id : Combination of transaction hash and log index
  • market : Id of the market for which this snapshot is created
  • inputTokenBalances : Value of inputTokenBalances of Market entity
  • outputTokenTotalSupply : same as above
  • blockNumber : Number of the block in which this transaction was executed
  • timestamp : Timestamp of above block
  • transactionIndexInBlock : Index of the transaction in the block. It can be used to debug subgraphs
  • logIndex : Log index of the event being processed

Position

This is the main entity which stores a position of a user in a market. Every time there is an action by a user which changes this user's investment or debt balance in the market we update this position and we create a PositionSnapshot entity to store previous values. Purpose of PositionSnapshot is to keep a history of changes in position of a user. It has following properties:
  • id : Combination of accountPositionId and autoIncrement number. This autoIncrement number is a value of positionCounter property of AccountPosition entity when creating this position entity
  • accountPosition : Id of the AccountPosition entity
  • account : Id of the Account entity which is created for the user address who created this position
  • accountAddress : It is the address of the user who created this position. This can be used to filter positions based on addresses
  • market : Id of the Market entity in which the user's position is created
  • marketAddress : Smart contract address of the market. This can be used to filter positions based on market addresses
  • positionType : One of the values of PositionType enum
  • outputTokenBalance : Latest balance of the user of the market's outputToken
  • inputTokenBalances : Balances of the input tokens that can be redeemed by sending the outputTokenBalance back to the market. Because of limitations in how subgraph mapping code makes contract calls, the values of inputTokenBalances are based on the market's state recorded by the relevant smart contract at the block level (after all the transaction in the block have been executed to update the smart contract address). This can cause an inaccurate value of inputTokenBalances for specific transactions which are followed by other transactions that affect the market's balances in the same block. We are working with The Graph to address this limitation and increase accuracy. Stored in TokenBalance format
  • rewardTokenBalances : Balances of tokens that are provided by the market as rewards to users (e.g. yield farming markets). These values are also subject to the same limitation as inputTokenBalances. Stored in TokenBalance format
  • transferredTo : List of addresses to which the user has part/full balance of outputToken. This can be used to connect movement of funds from one market to another while processing this data
  • closed : Status of the position. More on this below
  • blockNumber : Number of the block at which this position was created
  • timestamp : Timestamp of above block
  • historyCounter : An increamenting counter that is used as suffix to generate PositionSnapshot id
Closed Positions
A position is considered closed when:
  • A user has redeemed all the funds from the market and his outputTokenBalance has become 0 for a postion of type INVESTMENT
  • A user has repaid all the dues to the market and due amount becomes 0 for a position of type DEBT
In case a user again deposits funds to the market then we create a new Position instead of updating existing closed position. This way we keep track of historical positions as well.

AccountPosition

This entity is used to keep track of all historical positions of a user in a market. We cannot use the same ID for a user's new position after the existing one has been closed. To be able to fetch the current open position for a user + market + position type we need the ID to be dependent on only these three things. These three things will be the same for all positions of a specific user in a specfic market therefore we need this AccountPosition entity.
In this entity we keep a counter which keeps increasing as new positions are created by the same user on the same market of the same type. It has the following properties:
  • id : Combination of user address, market address and position type
  • positionCounter : Incrementing counter used as a suffix to create id for Position entity

Transaction

This entity is used to track parameters of interaction that changes a user's position. We also include gasUsed and gasPrice in this entity, it can be used to calculate the cost of transaction when processing this data. It has the following properties:
  • id : Combination of account address, transaction hash and log index
  • transactionHash : Hash of transaction. Can be used to debug subgraphs or get other information from the blockchain if required
  • market : Id of the market on which this transaction was done
  • marketSnapshot : Id of MarketSnapshot entity created just before creating this transaction entity
  • from : Account entity for address which sent this transaction. A transaction.from is always transaction origin (EOA) because outside EVM transaction.origin and msg.sender are same
  • to : Account entity for the address to which this transaction was sent
  • transactionType : One of the values of TransactionType enum
  • inputTokenAmounts : Amounts of input tokens that are being deposited or withdrawn in this transaction. Stored in TokenBalance format
  • outputTokenAmount : Amount of output token that is being deposited or withdrawn in this transaction
  • rewardTokenAmounts : Amounts of reward tokens that are being withdrawn in this transaction. Stored in TokenBalance format
  • transferredFrom : It is null by default. In case someone transfers his output token to the current user then we populate the address of that someone in this property
  • transferredTo : It is null by default. In case the current user transfers his output token to someone then we populate the address of that someone in this property
  • gasUsed : Amount of gas used in executing this transaction
  • gasPrice : Price of per unit of gas in gwei while executing this transaction
  • blockNumber : Number of the block in which this transaction was executed
  • timestamp : Timestamp of above block
  • transactionIndexInBlock : Index of transaction in the block. It can be used to debug subgraphs

PositionSnapshot

As described above this entity is used to store values of the Position entity before every update to it. It has following properties:
  • id : Combination of position id and historyCounter value in position entity
  • position : Id of the position for which this snapshot is created
  • transaction : Id of the transaction by which the above position is updated
  • outputTokenBalance : Value of outputTokenBalance of Position entity when creating this snapshot
  • inputTokenBalances : Same as above
  • rewardTokenBalances : Same as above
  • transferredTo : Same as above

Volumetric data

In addition to tracking user positions in protocol, we also want to analyze how protocol performs overall. For that purpose we need to track aggregated volume data. We dedcided to use daily granularity, in that way we can derive all kinds of interesting statistics. There is one enitity for all the volumetric data, shown below.
MarketDayData
  • id: marketAddress + dayId. Day ID is day's ordinal number
  • timestamp: timestamp of the first trade of the day
  • market: id of the market which is tracked
  • inputTokensDailySwapInVolume: amount of input tokens swapped in, in TokenBalance format
  • inputTokensDailySwapOutVolume: amount of input tokens swapped out, in TokenBalance format
  • inputTokenTotalBalances: total amount of reserves per input token on this day, in TokenBalance format
  • inputTokenDailyInflow: reserve amount inflow per input token this day, in TokenBalance format
  • inputTokenDailyOutflow: reserve amount outflow per input token this day, in TokenBalance format
  • outputTokenTotalBalance: total balance of LP tokens on this day
  • outputTokenDailyInflowVolume: amount of LP tokens minted this day
  • outputTokenDailyOutflowVolume: amount of LP tokens burned this day
  • protocolFee: fee in base points, applied to swap-in amount, taken by protocol
  • feesGenerated: amount of fees generated this day, per token in TokenBalance format
  • dailySwapTXs: number of TXs this day swap
  • dailyMintTXs: number of TXs this day mint
  • dailyBurnTXs: number of TXs this day burn
  • dayId: dayId - timestamp/86400

Protocol Specific Entities

Along with our common schema you can also define your own protocol specific entities to keep track of intermediate state changes of a protocol smart contract. We will explain what we mean by this in our walk though of SushiSwap subgraph development.