Redistribution
This document provides a comprehensive guide on how to redistribute your dApp's voting power and extra rewards to its users.
Integration Flow
The redistribution integration flow works as follows:
For dApps:
- Set EOL Delegate which represents your dApp properly.
- The EOL Delegate enables the redistribution on-chain.
- Write script code for a redistribution adapter which determines the redistribution ratios for each user of the dApp.
- Submit a PR for the redistribution adapter.
For the Mitosis team:
- Review and merge the PR from the dApp.
- Integrate the adapter into our redistribution workflow.
- After integration, all voting power and EOL rewards are redistributed to users according to the adapter.
Enable a Redistribution
Your EOL Delegate should enable the redistribution. After enabling redistribution, the EOL Delegate can't claim rewards or get voting power anymore. Instead, the rewards and voting power will be redistributed to the dApp's users.
// See full code: https://github.com/mitosis-org/protocol-public/blob/main/src/interfaces/hub/IMitosis.sol
interface IMitosis {
/**
* @notice Returns if a redistribution is enabled for `account`
* @param account The account address
*/
function isRedistributionEnabled(address account) external view returns (bool);
/**
* @notice Enables redistribution for the `msg.sender`
*/
function enableRedistribution() external;
}
The EOL Delegate can enable redistribution through enableRedistribution()
on the Mitosis contract.
For example:
cast send <Mitosis contract address> "enableRedistribution()" --private-key <Your EOL Delegate's private key>
Redistribution Adapter
You should develop a redistribution adapter to determine the redistribution ratios for each user of the dApp.
The adapter should be a function that satisfies:
Input
- Block Height
- Block Timestamp
Output
- Block Height
- Block Timestamp
- Source Address of Voting Power & Rewards
- It means the source contract of voting power and rewards delegated to the EOL Delegate.
- Account Address
- Weight
- The account will receive the voting power and rewards equivalent to
weight / sum of all weights
.
- The account will receive the voting power and rewards equivalent to
The adapter might need to access indexed data for your dApp in its implementation.
You can use Subgraph or your own indexer server. Subgraph is the recommended approach.
We have a private repository for the adapters: https://github.com/mitosis-org/redistribution-adapters.
You can develop the adapter and submit a PR to the repo. Please contact us to be invited to the repository.
For better understanding, we also provide an integration example for Uniswap V2:
- Example Adapter: https://github.com/mitosis-org/redistribution-adapters/tree/main/badnet/uniswap-v2
- Example Subgraph: https://github.com/mitosis-org/uniswap-v2-subgraph
- Example Output: https://github.com/mitosis-org/redistribution-adapters/blob/main/badnet/examples.csv
This is the example of the adapter for Uniswap V2:
import fs from "fs";
import { write } from "fast-csv";
import { keccak256 } from "js-sha3";
import { OutputDataSchemaRow } from "../../interface";
const GRAPHQL_ENDPOINT =
"https://api.goldsky.com/api/public/project_cm3epvtvsc51t01xigvi86klb/subgraphs/uniswap-v2/1.0.0/gn";
const makeLiquidityPositionsQuery = (blockNumber: number, next = "") => `query {
liquidityPositions (
block: {number: ${blockNumber}},
first: 1000,
where: { id_gt: "${next}" }
) {
id
user {
id
}
liquidityTokenBalance
}
}`;
interface User {
id: string;
}
interface LiquidityPosition {
id: string;
user: User;
liquidityTokenBalance: string;
}
interface LiquidityPositionsResponse {
liquidityPositions: LiquidityPosition[];
}
async function post<T = any>(url: string, query: any): Promise<{ data: T }> {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({ query }),
});
return response.json();
}
const toOutput = (
blockNumber: number,
blockTimestamp: number,
{ liquidityPositions }: LiquidityPositionsResponse,
): OutputDataSchemaRow[] => liquidityPositions.map((v) => {
return {
block_number: blockNumber,
block_timestamp: blockTimestamp,
source_address: v.id.split("-")[0],
account_address: v.user.id,
weight: BigInt(Math.floor(Number(v.liquidityTokenBalance) * 10 ** 18 /* UniswapV2ERC20 decimals is 18 */)).toString(),
};
});
export const getUserWeightsByBlock = async (
blockNumber: number,
blockTimestamp: number,
): Promise<OutputDataSchemaRow[]> => {
let next = "";
let output: OutputDataSchemaRow[] = [];
while (true) {
const { data: resp } = await post<LiquidityPositionsResponse>(
GRAPHQL_ENDPOINT,
makeLiquidityPositionsQuery(blockNumber, next)
);
if (resp.liquidityPositions.length === 0) break;
output = output.concat(toOutput(blockNumber, blockTimestamp, resp));
next = resp.liquidityPositions[resp.liquidityPositions.length - 1].id;
}
return output;
};