@allo-team/allo-v2-sdk
Version:
758 lines (626 loc) • 18.6 kB
text/typescript
import {
Chain,
PublicClient,
Transport,
encodeAbiParameters,
encodeFunctionData,
extractChain,
getContract,
parseAbiParameters,
} from "viem";
import { Allo } from "../../Allo/Allo";
import { abi as alloAbi } from "../../Allo/allo.config";
import { create } from "../../Client/Client";
import {
ConstructorArgs,
DeployParams,
Metadata,
TransactionData,
ZERO_ADDRESS,
} from "../../Common/types";
import { supportedChains } from "../../chains.config";
import { NATIVE } from "../../types";
import { PayoutSummary, Status } from "../types";
import {
abi as directAbi,
bytecode as directBytecode,
} from "./donationVotingDirect.config";
import {
abi as vaultAbi,
bytecode as vaultBytecode,
} from "./donationVotingVault.config";
import {
Allocation,
Claim,
Distribution,
InitializeData,
Recipient,
RegisterData,
StrategyType,
} from "./types";
export class DonationVotingMerkleDistributionStrategy {
private client: PublicClient<Transport, Chain>;
private contract: any;
private strategy: `0x${string}` | undefined;
private poolId: bigint;
private allo: Allo;
constructor({ chain, rpc, address, poolId }: ConstructorArgs) {
const usedChain = extractChain({
chains: supportedChains,
id: chain as any,
});
this.client = create(usedChain, rpc);
this.allo = new Allo({ chain, rpc }); // to call allocate
if (address) {
this.contract = getContract({
address: address,
abi: vaultAbi,
client: {
public: this.client,
}
});
this.strategy = address;
}
this.poolId = poolId || BigInt(-1);
}
public async getAllo(): Promise<Allo> {
return this.allo;
}
public async setPoolId(poolId: bigint): Promise<void> {
this.poolId = poolId;
const strategyAddress = await this.allo.getStrategy(poolId);
this.setContract(strategyAddress as `0x${string}`);
}
public setContract(address: `0x${string}`): void {
this.contract = getContract({
address: address,
abi: vaultAbi,
client: {
public: this.client,
}
});
this.strategy = address;
}
// Validation functions
private checkPoolId(): void {
if (this.poolId === BigInt(-1))
throw new Error(
"DonationVotingMerkleDistributionStrategy: No poolId provided. Please call `setPoolId` first.",
);
}
private checkStrategy(): void {
if (!this.strategy)
throw new Error(
"DonationVotingMerkleDistributionStrategy: No strategy address provided. Please call `setContract` first.",
);
}
/* Read Functions */
public async getNative(): Promise<string> {
this.checkStrategy();
const native = await this.contract.read.NATIVE();
return native;
}
public async getPermit2(): Promise<string> {
this.checkStrategy();
const permit2 = await this.contract.read.PERMIT2();
return permit2;
}
/* Public Storage Variables */
public async getDistributionMetadata(): Promise<Metadata> {
this.checkStrategy();
const metadata: Metadata = await this.contract.read.distributionMetadata();
return metadata;
}
public async useRegistryAnchor(): Promise<boolean> {
this.checkStrategy();
const anchor = await this.contract.read.useRegistryAnchor();
return anchor;
}
public async metadataRequired(): Promise<boolean> {
this.checkStrategy();
const required = await this.contract.read.metadataRequired();
return required;
}
public async distributionStarted(): Promise<boolean> {
this.checkStrategy();
const started = await this.contract.read.distributionStarted();
return started;
}
public async registrationStartTime(): Promise<bigint> {
this.checkStrategy();
const startTime = await this.contract.read.registrationStartTime();
return startTime;
}
public async registrationEndTime(): Promise<bigint> {
this.checkStrategy();
const endTime = await this.contract.read.registrationEndTime();
return endTime;
}
public async allocationStartTime(): Promise<bigint> {
this.checkStrategy();
const startTime = await this.contract.read.allocationStartTime();
return startTime;
}
public async allocationEndTime(): Promise<bigint> {
this.checkStrategy();
const endTime = await this.contract.read.allocationEndTime();
return endTime;
}
public async totalPayoutAmount(): Promise<bigint> {
this.checkStrategy();
const amount = await this.contract.read.totalPayoutAmount();
return amount;
}
public async recipientsCounter(): Promise<bigint> {
this.checkStrategy();
const counter = await this.contract.read.recipientsCounter();
return counter;
}
public async getMerkleRoot(): Promise<string> {
this.checkStrategy();
const root = await this.contract.read.merkleRoot();
return root;
}
public async statusesBitMap(index: bigint): Promise<bigint> {
this.checkStrategy();
const bitMap = await this.contract.read.statusesBitMap([index]);
return bitMap;
}
public async recipientToStatusIndexes(recipient: string): Promise<bigint[]> {
this.checkStrategy();
const indexes = await this.contract.read.recipientToStatusIndexes([
recipient,
]);
return indexes;
}
public async isTokenAllowed(token: string): Promise<boolean> {
this.checkStrategy();
const allowed = await this.contract.read.allowedTokens(token);
return allowed;
}
public async getClaims(recipient: string, token: string): Promise<bigint> {
const claims = await this.contract.read.claims([recipient, token]);
return claims;
}
public async getTotalClaimableAmount(recipient: string): Promise<bigint> {
const claims = await this.contract.read.totalClaimableAmount([recipient]);
return claims;
}
/* Public Read Functions */
// TODO: FIX FROM HERE
public async getPayouts(
recipientIds: string[],
data: string[],
): Promise<PayoutSummary[]> {
this.checkStrategy();
const payouts = await this.contract.read.getPayouts([recipientIds, data]);
const payoutSummary: PayoutSummary[] = payouts.map((payout: any) => {
return {
address: payout.recipientAddress,
amount: payout.amount,
};
});
return payoutSummary;
}
public async getPoolAmount(): Promise<bigint> {
this.checkStrategy();
const amount = await this.contract.read.getPoolAmount();
return amount;
}
public async getPoolId(): Promise<bigint> {
this.checkStrategy();
const id = await this.contract.read.getPoolId();
return id;
}
public async getRecipient(recipientId: string): Promise<Recipient> {
this.checkStrategy();
const recipient = await this.contract.read.getRecipient([recipientId]);
return recipient;
}
public async getRecipientStatus(recipientId: string): Promise<Status> {
this.checkStrategy();
const status = await this.contract.read.getRecipientStatus([recipientId]);
return status;
}
public async getStrategyId(): Promise<string> {
this.checkStrategy();
const id = await this.contract.read.getStrategyId();
return id;
}
public async hasBeenDistributed(index: bigint): Promise<boolean> {
this.checkStrategy();
const distributed = await this.contract.read.hasBeenDistributed([index]);
return distributed;
}
public async isDistributionSet(): Promise<boolean> {
this.checkStrategy();
const set = await this.contract.read.isDistributionSet();
return set;
}
public async isPoolActive(): Promise<boolean> {
this.checkStrategy();
const active = await this.contract.read.isPoolActive();
return active;
}
public async isValidAllocator(allocator: `0x${string}`): Promise<boolean> {
this.checkStrategy();
const valid = await this.contract.read.isValidAllocator([allocator]);
return valid;
}
/**
* Write functions
*/
/**
*
* @param strategyType - StrategyType ("Vault" | "Direct")
* @returns DeployParams {abi, bytecode}
*/
public getDeployParams(strategyType: string): DeployParams {
if (
strategyType !== StrategyType.Vault &&
strategyType !== StrategyType.Direct
) {
throw new Error("Invalid strategy type");
}
const version =
strategyType === StrategyType.Vault
? "DonationVotingMerkleDistributionVaultStrategyv2.0"
: "DonationVotingMerkleDistributionDirectTransferStrategyv2.0";
const bytecode =
strategyType === StrategyType.Vault ? vaultBytecode : directBytecode;
const abi = strategyType === StrategyType.Vault ? vaultAbi : directAbi;
const constructorArgs: `0x${string}` = encodeAbiParameters(
parseAbiParameters("address, string"),
[this.allo.address(), version],
);
const constructorArgsNo0x = constructorArgs.slice(2);
return {
abi: abi,
bytecode: (bytecode + constructorArgsNo0x) as unknown as `0x${string}`,
};
}
public async getInitializeData(data: InitializeData): Promise<`0x${string}`> {
const encodedData: `0x${string}` = encodeAbiParameters(
parseAbiParameters(
"(bool, bool, uint64, uint64, uint64, uint64, address[])",
),
[
[
data.useRegistryAnchor,
data.metadataRequired,
data.registrationStartTime,
data.registrationEndTime,
data.allocationStartTime,
data.allocationEndTime,
data.allowedTokens,
],
],
);
return encodedData;
}
/**
*
* @param data - Allocation: (address,(((address,uint256),uint256,uint256),bytes))
* @returns `0x${string}`
*/
public getEncodedAllocation(data: Allocation): `0x${string}` {
const encoded: `0x${string}` = encodeAbiParameters(
parseAbiParameters(
"address,uint8,(((address,uint256),uint256,uint256),bytes)",
),
[
data.recipientId,
data.permitType,
[
[
[
data.permit2Data.permit.permitted.token,
data.permit2Data.permit.permitted.amount,
],
data.permit2Data.permit.nonce,
data.permit2Data.permit.deadline,
],
data.permit2Data.signature,
],
],
);
return encoded;
}
/**
*
* @param allocation - Allocation: (address,PermitType,(((address,uint256),uint256,uint256),bytes32))
* @returns TransactionData: {to: `0x${string}`, data: `0x${string}`, value: string}
*/
public getAllocateData(allocation: Allocation): TransactionData {
this.checkPoolId();
const token = allocation.permit2Data.permit.permitted.token;
const amount = allocation.permit2Data.permit.permitted.amount;
const encoded: `0x${string}` = this.getEncodedAllocation(allocation);
const encodedData = encodeFunctionData({
abi: alloAbi,
functionName: "allocate",
args: [this.poolId, encoded],
});
return {
to: this.allo.address(),
data: encodedData,
value: token.toLowerCase() === NATIVE ? amount.toString() : "0",
};
}
/**
*
* @param allocations - Array of Allocation: (address,(((address,uint256),uint256,uint256),bytes32))
* @returns TransactionData: {to: `0x${string}`, data: `0x${string}`, value: string}
*/
public getBatchAllocateData(allocations: Allocation[]): TransactionData {
this.checkPoolId();
const encodedParams: `0x${string}`[] = [];
allocations.forEach((allocation) => {
const encoded: `0x${string}` = this.getEncodedAllocation(allocation);
encodedParams.push(encoded);
});
const poolIds: bigint[] = Array(encodedParams.length).fill(this.poolId);
const encodedData = encodeFunctionData({
abi: alloAbi,
functionName: "batchAllocate",
args: [poolIds, encodedParams],
});
return {
to: this.allo.address(),
data: encodedData,
value: "0",
};
}
/**
*
* @param poolIds - Array of poolIds
* @param allocations - Array of Allocation: (address,(((address,uint256),uint256,uint256),bytes32))
* @returns TransactionData: {to: `0x${string}`, data: `0x${string}`, value: string}
*/
public getBatchAllocateDataMultiplePools(
poolIds: bigint[],
allocations: Allocation[],
): TransactionData {
if (poolIds.length !== allocations.length) {
throw new Error(
"DonationVotingMerkleDistributionStrategy: Length of poolIds and allocations must be equal",
);
}
const encodedParams: `0x${string}`[] = [];
allocations.forEach((allocation) => {
const encoded: `0x${string}` = this.getEncodedAllocation(allocation);
encodedParams.push(encoded);
});
const encodedData = encodeFunctionData({
abi: alloAbi,
functionName: "batchAllocate",
args: [poolIds, encodedParams],
});
return {
to: this.allo.address(),
data: encodedData,
value: "0",
};
}
/**
*
* @param data - (address, address, Metadata)
* @returns
*/
public getRegisterRecipientData(data: RegisterData): TransactionData {
this.checkPoolId();
const encoded: `0x${string}` = encodeAbiParameters(
parseAbiParameters("address, address, (uint256, string)"),
[
data.registryAnchor || ZERO_ADDRESS,
data.recipientAddress,
[data.metadata.protocol, data.metadata.pointer],
],
);
const encodedData = encodeFunctionData({
abi: alloAbi,
functionName: "registerRecipient",
args: [this.poolId, encoded],
});
return {
to: this.allo.address(),
data: encodedData,
value: "0",
};
}
/**
* Batch register recipients
*
* @param data - Array of RegisterDataDonationVoting
*
* @returns TransactionData
*/
public getBatchRegisterRecipientData(data: RegisterData[]): TransactionData {
this.checkPoolId();
const encodedParams: `0x${string}`[] = [];
data.forEach((registerData) => {
const encoded: `0x${string}` = encodeAbiParameters(
parseAbiParameters("address, address, (uint256, string)"),
[
registerData.registryAnchor || ZERO_ADDRESS,
registerData.recipientAddress,
[registerData.metadata.protocol, registerData.metadata.pointer],
],
);
encodedParams.push(encoded);
});
const poolIds: bigint[] = Array(encodedParams.length).fill(this.poolId);
const encodedData = encodeFunctionData({
abi: alloAbi,
functionName: "batchRegisterRecipient",
args: [poolIds, encodedParams],
});
return {
to: this.allo.address(),
data: encodedData,
value: "0",
};
}
/**
* Fund the pool
*
* @param amount - Amount to fund the pool
*
* @returns TransactionData
*/
public fundPool(amount: bigint): TransactionData {
this.checkPoolId();
const encodedData = encodeFunctionData({
abi: alloAbi,
functionName: "fundPool",
args: [this.poolId, amount],
});
return {
to: this.allo.address(),
data: encodedData,
value: "0",
};
}
/**
* Distribute to the recipients
*
* @param recipientIds - Array of recipientIds
* @param data - Array of Distribution
*
* @returns TransactionData
*/
public distribute(
recipientIds: `0x${string}`[],
// (uint256 _poolId, address[] memory _recipientIds, bytes memory _data)
data: Distribution[],
): TransactionData {
this.checkPoolId();
const encodeDistribution = encodeAbiParameters(
parseAbiParameters(
"(uint256 index, address recipientId, uint256 amount, bytes32[] merkleProof)[]",
),
[data],
);
const encodedData = encodeFunctionData({
abi: alloAbi,
functionName: "distribute",
args: [this.poolId, recipientIds, encodeDistribution],
});
return {
to: this.allo.address(),
data: encodedData,
value: "0",
};
}
/**
* Get the claim function encoded data
*
* @param claims - Array of claims
*
* @returns - Encoded transaction data
*/
public getClaimData(claims: Claim[]): TransactionData {
this.checkPoolId();
const encodedData = encodeFunctionData({
abi: vaultAbi,
functionName: "claim",
args: [claims],
});
return {
to: this.strategy!,
data: encodedData,
value: "0",
};
}
/**
* Provides a function to batch together multiple calls in a single external call
*
* @param data - Array of encoded data
*
* @returns - Encoded transaction data
*/
public multicall(data: `0x${string}`[]): TransactionData {
this.checkPoolId();
const encodedData = encodeFunctionData({
abi: vaultAbi,
functionName: "multicall",
args: [data],
});
return {
to: this.strategy!,
data: encodedData,
value: "0",
};
}
/**
* Review recipients
*
* @param statuses - Array of status indexes and statusRows
*
* @returns TransactionData
*/
public reviewRecipients(
statuses: { index: bigint; statusRow: bigint }[],
refRecipientsCounter: bigint,
): TransactionData {
const data = encodeFunctionData({
abi: vaultAbi,
functionName: "reviewRecipients",
args: [statuses, refRecipientsCounter],
});
return {
to: this.strategy!,
data: data,
value: "0",
};
}
public updateDistribution(
merkleRoot: `0x${string}`,
distributionMetadata: Metadata,
): TransactionData {
const data = encodeFunctionData({
abi: vaultAbi,
functionName: "updateDistribution",
args: [merkleRoot, distributionMetadata],
});
return {
to: this.strategy!,
data: data,
value: "0",
};
}
public updatePoolTimestamps(
registrationStartTime: bigint,
registrationEndTime: bigint,
allocationStartTime: bigint,
allocationEndTime: bigint,
): TransactionData {
const data = encodeFunctionData({
abi: vaultAbi,
functionName: "updatePoolTimestamps",
args: [
registrationStartTime,
registrationEndTime,
allocationStartTime,
allocationEndTime,
],
});
return {
to: this.strategy!,
data: data,
value: "0",
};
}
public withdraw(address: `0x${string}`): TransactionData {
const data = encodeFunctionData({
abi: vaultAbi,
functionName: "withdraw",
args: [address],
});
return {
to: this.strategy!,
data: data,
value: "0",
};
}
}