From 55e3c50b1a7a4d258f944b96595d943e5adfdec7 Mon Sep 17 00:00:00 2001 From: 0xdapper <94534135+0xdapper@users.noreply.github.com> Date: Tue, 28 Feb 2023 21:32:51 +0530 Subject: [PATCH] feat(gearbox): add rewards (#2384) --- .../contracts/abis/airdrop-distributor.json | 320 ++++++++++++++ .../contracts/ethers/AirdropDistributor.ts | 404 ++++++++++++++++++ .../factories/AirdropDistributor__factory.ts | 338 +++++++++++++++ .../contracts/ethers/factories/index.ts | 1 + src/apps/gearbox/contracts/ethers/index.ts | 2 + src/apps/gearbox/contracts/index.ts | 5 + ...arbox.rewards.contract-position-fetcher.ts | 47 ++ .../ethereum/gearbox.rewards.merkle-cache.ts | 73 ++++ src/apps/gearbox/gearbox.module.ts | 6 +- .../network-provider.registry.ts | 2 +- 10 files changed, 1196 insertions(+), 2 deletions(-) create mode 100644 src/apps/gearbox/contracts/abis/airdrop-distributor.json create mode 100644 src/apps/gearbox/contracts/ethers/AirdropDistributor.ts create mode 100644 src/apps/gearbox/contracts/ethers/factories/AirdropDistributor__factory.ts create mode 100644 src/apps/gearbox/ethereum/gearbox.rewards.contract-position-fetcher.ts create mode 100644 src/apps/gearbox/ethereum/gearbox.rewards.merkle-cache.ts diff --git a/src/apps/gearbox/contracts/abis/airdrop-distributor.json b/src/apps/gearbox/contracts/abis/airdrop-distributor.json new file mode 100644 index 000000000..2456cfe1f --- /dev/null +++ b/src/apps/gearbox/contracts/abis/airdrop-distributor.json @@ -0,0 +1,320 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "addressProvider", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot_", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ClaimedData[]", + "name": "alreadyClaimed", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AlreadyClaimedFinishedException", + "type": "error" + }, + { + "inputs": [], + "name": "TreasuryOnlyException", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bool", + "name": "historic", + "type": "bool" + } + ], + "name": "Claimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "oldRoot", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newRoot", + "type": "bytes32" + } + ], + "name": "RootUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint8", + "name": "campaignId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokenAllocated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "totalAmount", + "type": "uint256" + }, + { + "internalType": "bytes32[]", + "name": "merkleProof", + "type": "bytes32[]" + } + ], + "name": "claim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "claimed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint8", + "name": "campaignId", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct DistributionData[]", + "name": "data", + "type": "tuple[]" + } + ], + "name": "emitDistributionEvents", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "merkleRoot", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "treasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ClaimedData[]", + "name": "alreadyClaimed", + "type": "tuple[]" + } + ], + "name": "updateHistoricClaims", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "newRoot", + "type": "bytes32" + } + ], + "name": "updateMerkleRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/apps/gearbox/contracts/ethers/AirdropDistributor.ts b/src/apps/gearbox/contracts/ethers/AirdropDistributor.ts new file mode 100644 index 000000000..7c38b026a --- /dev/null +++ b/src/apps/gearbox/contracts/ethers/AirdropDistributor.ts @@ -0,0 +1,404 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BigNumberish, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PopulatedTransaction, + Signer, + utils, +} from 'ethers'; +import type { FunctionFragment, Result, EventFragment } from '@ethersproject/abi'; +import type { Listener, Provider } from '@ethersproject/providers'; +import type { TypedEventFilter, TypedEvent, TypedListener, OnEvent, PromiseOrValue } from './common'; + +export type ClaimedDataStruct = { + account: PromiseOrValue; + amount: PromiseOrValue; +}; + +export type ClaimedDataStructOutput = [string, BigNumber] & { + account: string; + amount: BigNumber; +}; + +export type DistributionDataStruct = { + account: PromiseOrValue; + campaignId: PromiseOrValue; + amount: PromiseOrValue; +}; + +export type DistributionDataStructOutput = [string, number, BigNumber] & { + account: string; + campaignId: number; + amount: BigNumber; +}; + +export interface AirdropDistributorInterface extends utils.Interface { + functions: { + 'claim(uint256,address,uint256,bytes32[])': FunctionFragment; + 'claimed(address)': FunctionFragment; + 'emitDistributionEvents((address,uint8,uint256)[])': FunctionFragment; + 'merkleRoot()': FunctionFragment; + 'owner()': FunctionFragment; + 'renounceOwnership()': FunctionFragment; + 'token()': FunctionFragment; + 'transferOwnership(address)': FunctionFragment; + 'treasury()': FunctionFragment; + 'updateHistoricClaims((address,uint256)[])': FunctionFragment; + 'updateMerkleRoot(bytes32)': FunctionFragment; + }; + + getFunction( + nameOrSignatureOrTopic: + | 'claim' + | 'claimed' + | 'emitDistributionEvents' + | 'merkleRoot' + | 'owner' + | 'renounceOwnership' + | 'token' + | 'transferOwnership' + | 'treasury' + | 'updateHistoricClaims' + | 'updateMerkleRoot', + ): FunctionFragment; + + encodeFunctionData( + functionFragment: 'claim', + values: [ + PromiseOrValue, + PromiseOrValue, + PromiseOrValue, + PromiseOrValue[], + ], + ): string; + encodeFunctionData(functionFragment: 'claimed', values: [PromiseOrValue]): string; + encodeFunctionData(functionFragment: 'emitDistributionEvents', values: [DistributionDataStruct[]]): string; + encodeFunctionData(functionFragment: 'merkleRoot', values?: undefined): string; + encodeFunctionData(functionFragment: 'owner', values?: undefined): string; + encodeFunctionData(functionFragment: 'renounceOwnership', values?: undefined): string; + encodeFunctionData(functionFragment: 'token', values?: undefined): string; + encodeFunctionData(functionFragment: 'transferOwnership', values: [PromiseOrValue]): string; + encodeFunctionData(functionFragment: 'treasury', values?: undefined): string; + encodeFunctionData(functionFragment: 'updateHistoricClaims', values: [ClaimedDataStruct[]]): string; + encodeFunctionData(functionFragment: 'updateMerkleRoot', values: [PromiseOrValue]): string; + + decodeFunctionResult(functionFragment: 'claim', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'claimed', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'emitDistributionEvents', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'merkleRoot', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'owner', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'renounceOwnership', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'token', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'transferOwnership', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'treasury', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'updateHistoricClaims', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'updateMerkleRoot', data: BytesLike): Result; + + events: { + 'Claimed(address,uint256,bool)': EventFragment; + 'OwnershipTransferred(address,address)': EventFragment; + 'RootUpdated(bytes32,bytes32)': EventFragment; + 'TokenAllocated(address,uint8,uint256)': EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: 'Claimed'): EventFragment; + getEvent(nameOrSignatureOrTopic: 'OwnershipTransferred'): EventFragment; + getEvent(nameOrSignatureOrTopic: 'RootUpdated'): EventFragment; + getEvent(nameOrSignatureOrTopic: 'TokenAllocated'): EventFragment; +} + +export interface ClaimedEventObject { + account: string; + amount: BigNumber; + historic: boolean; +} +export type ClaimedEvent = TypedEvent<[string, BigNumber, boolean], ClaimedEventObject>; + +export type ClaimedEventFilter = TypedEventFilter; + +export interface OwnershipTransferredEventObject { + previousOwner: string; + newOwner: string; +} +export type OwnershipTransferredEvent = TypedEvent<[string, string], OwnershipTransferredEventObject>; + +export type OwnershipTransferredEventFilter = TypedEventFilter; + +export interface RootUpdatedEventObject { + oldRoot: string; + newRoot: string; +} +export type RootUpdatedEvent = TypedEvent<[string, string], RootUpdatedEventObject>; + +export type RootUpdatedEventFilter = TypedEventFilter; + +export interface TokenAllocatedEventObject { + account: string; + campaignId: number; + amount: BigNumber; +} +export type TokenAllocatedEvent = TypedEvent<[string, number, BigNumber], TokenAllocatedEventObject>; + +export type TokenAllocatedEventFilter = TypedEventFilter; + +export interface AirdropDistributor extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: AirdropDistributorInterface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined, + ): Promise>; + + listeners(eventFilter?: TypedEventFilter): Array>; + listeners(eventName?: string): Array; + removeAllListeners(eventFilter: TypedEventFilter): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + claim( + index: PromiseOrValue, + account: PromiseOrValue, + totalAmount: PromiseOrValue, + merkleProof: PromiseOrValue[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + claimed(arg0: PromiseOrValue, overrides?: CallOverrides): Promise<[BigNumber]>; + + emitDistributionEvents( + data: DistributionDataStruct[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + merkleRoot(overrides?: CallOverrides): Promise<[string]>; + + owner(overrides?: CallOverrides): Promise<[string]>; + + renounceOwnership(overrides?: Overrides & { from?: PromiseOrValue }): Promise; + + token(overrides?: CallOverrides): Promise<[string]>; + + transferOwnership( + newOwner: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + treasury(overrides?: CallOverrides): Promise<[string]>; + + updateHistoricClaims( + alreadyClaimed: ClaimedDataStruct[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + updateMerkleRoot( + newRoot: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + }; + + claim( + index: PromiseOrValue, + account: PromiseOrValue, + totalAmount: PromiseOrValue, + merkleProof: PromiseOrValue[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + claimed(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + emitDistributionEvents( + data: DistributionDataStruct[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + merkleRoot(overrides?: CallOverrides): Promise; + + owner(overrides?: CallOverrides): Promise; + + renounceOwnership(overrides?: Overrides & { from?: PromiseOrValue }): Promise; + + token(overrides?: CallOverrides): Promise; + + transferOwnership( + newOwner: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + treasury(overrides?: CallOverrides): Promise; + + updateHistoricClaims( + alreadyClaimed: ClaimedDataStruct[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + updateMerkleRoot( + newRoot: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + callStatic: { + claim( + index: PromiseOrValue, + account: PromiseOrValue, + totalAmount: PromiseOrValue, + merkleProof: PromiseOrValue[], + overrides?: CallOverrides, + ): Promise; + + claimed(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + emitDistributionEvents(data: DistributionDataStruct[], overrides?: CallOverrides): Promise; + + merkleRoot(overrides?: CallOverrides): Promise; + + owner(overrides?: CallOverrides): Promise; + + renounceOwnership(overrides?: CallOverrides): Promise; + + token(overrides?: CallOverrides): Promise; + + transferOwnership(newOwner: PromiseOrValue, overrides?: CallOverrides): Promise; + + treasury(overrides?: CallOverrides): Promise; + + updateHistoricClaims(alreadyClaimed: ClaimedDataStruct[], overrides?: CallOverrides): Promise; + + updateMerkleRoot(newRoot: PromiseOrValue, overrides?: CallOverrides): Promise; + }; + + filters: { + 'Claimed(address,uint256,bool)'( + account?: PromiseOrValue | null, + amount?: null, + historic?: PromiseOrValue | null, + ): ClaimedEventFilter; + Claimed( + account?: PromiseOrValue | null, + amount?: null, + historic?: PromiseOrValue | null, + ): ClaimedEventFilter; + + 'OwnershipTransferred(address,address)'( + previousOwner?: PromiseOrValue | null, + newOwner?: PromiseOrValue | null, + ): OwnershipTransferredEventFilter; + OwnershipTransferred( + previousOwner?: PromiseOrValue | null, + newOwner?: PromiseOrValue | null, + ): OwnershipTransferredEventFilter; + + 'RootUpdated(bytes32,bytes32)'(oldRoot?: null, newRoot?: PromiseOrValue | null): RootUpdatedEventFilter; + RootUpdated(oldRoot?: null, newRoot?: PromiseOrValue | null): RootUpdatedEventFilter; + + 'TokenAllocated(address,uint8,uint256)'( + account?: PromiseOrValue | null, + campaignId?: PromiseOrValue | null, + amount?: null, + ): TokenAllocatedEventFilter; + TokenAllocated( + account?: PromiseOrValue | null, + campaignId?: PromiseOrValue | null, + amount?: null, + ): TokenAllocatedEventFilter; + }; + + estimateGas: { + claim( + index: PromiseOrValue, + account: PromiseOrValue, + totalAmount: PromiseOrValue, + merkleProof: PromiseOrValue[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + claimed(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + emitDistributionEvents( + data: DistributionDataStruct[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + merkleRoot(overrides?: CallOverrides): Promise; + + owner(overrides?: CallOverrides): Promise; + + renounceOwnership(overrides?: Overrides & { from?: PromiseOrValue }): Promise; + + token(overrides?: CallOverrides): Promise; + + transferOwnership( + newOwner: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + treasury(overrides?: CallOverrides): Promise; + + updateHistoricClaims( + alreadyClaimed: ClaimedDataStruct[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + updateMerkleRoot( + newRoot: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + }; + + populateTransaction: { + claim( + index: PromiseOrValue, + account: PromiseOrValue, + totalAmount: PromiseOrValue, + merkleProof: PromiseOrValue[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + claimed(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + emitDistributionEvents( + data: DistributionDataStruct[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + merkleRoot(overrides?: CallOverrides): Promise; + + owner(overrides?: CallOverrides): Promise; + + renounceOwnership(overrides?: Overrides & { from?: PromiseOrValue }): Promise; + + token(overrides?: CallOverrides): Promise; + + transferOwnership( + newOwner: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + treasury(overrides?: CallOverrides): Promise; + + updateHistoricClaims( + alreadyClaimed: ClaimedDataStruct[], + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + updateMerkleRoot( + newRoot: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + }; +} diff --git a/src/apps/gearbox/contracts/ethers/factories/AirdropDistributor__factory.ts b/src/apps/gearbox/contracts/ethers/factories/AirdropDistributor__factory.ts new file mode 100644 index 000000000..c4b67eaec --- /dev/null +++ b/src/apps/gearbox/contracts/ethers/factories/AirdropDistributor__factory.ts @@ -0,0 +1,338 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Signer, utils } from 'ethers'; +import type { Provider } from '@ethersproject/providers'; +import type { AirdropDistributor, AirdropDistributorInterface, ClaimedDataStruct } from '../AirdropDistributor'; + +const _abi = [ + { + inputs: [ + { + internalType: 'address', + name: 'addressProvider', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'merkleRoot_', + type: 'bytes32', + }, + { + components: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + internalType: 'struct ClaimedData[]', + name: 'alreadyClaimed', + type: 'tuple[]', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'AlreadyClaimedFinishedException', + type: 'error', + }, + { + inputs: [], + name: 'TreasuryOnlyException', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bool', + name: 'historic', + type: 'bool', + }, + ], + name: 'Claimed', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: 'oldRoot', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'newRoot', + type: 'bytes32', + }, + ], + name: 'RootUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: true, + internalType: 'uint8', + name: 'campaignId', + type: 'uint8', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'TokenAllocated', + type: 'event', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'uint256', + name: 'totalAmount', + type: 'uint256', + }, + { + internalType: 'bytes32[]', + name: 'merkleProof', + type: 'bytes32[]', + }, + ], + name: 'claim', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'claimed', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'uint8', + name: 'campaignId', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + internalType: 'struct DistributionData[]', + name: 'data', + type: 'tuple[]', + }, + ], + name: 'emitDistributionEvents', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'merkleRoot', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'token', + outputs: [ + { + internalType: 'contract IERC20', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'treasury', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + internalType: 'struct ClaimedData[]', + name: 'alreadyClaimed', + type: 'tuple[]', + }, + ], + name: 'updateHistoricClaims', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'newRoot', + type: 'bytes32', + }, + ], + name: 'updateMerkleRoot', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +]; + +export class AirdropDistributor__factory { + static readonly abi = _abi; + static createInterface(): AirdropDistributorInterface { + return new utils.Interface(_abi) as AirdropDistributorInterface; + } + static connect(address: string, signerOrProvider: Signer | Provider): AirdropDistributor { + return new Contract(address, _abi, signerOrProvider) as AirdropDistributor; + } +} diff --git a/src/apps/gearbox/contracts/ethers/factories/index.ts b/src/apps/gearbox/contracts/ethers/factories/index.ts index 1eb2a0c69..7cd4a46e3 100644 --- a/src/apps/gearbox/contracts/ethers/factories/index.ts +++ b/src/apps/gearbox/contracts/ethers/factories/index.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ export { AccountFactory__factory } from './AccountFactory__factory'; +export { AirdropDistributor__factory } from './AirdropDistributor__factory'; export { ContractsRegister__factory } from './ContractsRegister__factory'; export { CreditManagerV2__factory } from './CreditManagerV2__factory'; export { DieselToken__factory } from './DieselToken__factory'; diff --git a/src/apps/gearbox/contracts/ethers/index.ts b/src/apps/gearbox/contracts/ethers/index.ts index 63c340c09..852f871ed 100644 --- a/src/apps/gearbox/contracts/ethers/index.ts +++ b/src/apps/gearbox/contracts/ethers/index.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ export type { AccountFactory } from './AccountFactory'; +export type { AirdropDistributor } from './AirdropDistributor'; export type { ContractsRegister } from './ContractsRegister'; export type { CreditManagerV2 } from './CreditManagerV2'; export type { DieselToken } from './DieselToken'; @@ -9,6 +10,7 @@ export type { PhantomToken } from './PhantomToken'; export type { PoolService } from './PoolService'; export * as factories from './factories'; export { AccountFactory__factory } from './factories/AccountFactory__factory'; +export { AirdropDistributor__factory } from './factories/AirdropDistributor__factory'; export { ContractsRegister__factory } from './factories/ContractsRegister__factory'; export { CreditManagerV2__factory } from './factories/CreditManagerV2__factory'; export { DieselToken__factory } from './factories/DieselToken__factory'; diff --git a/src/apps/gearbox/contracts/index.ts b/src/apps/gearbox/contracts/index.ts index 794943f5e..4b0a67f37 100644 --- a/src/apps/gearbox/contracts/index.ts +++ b/src/apps/gearbox/contracts/index.ts @@ -6,6 +6,7 @@ import { Network } from '~types/network.interface'; import { AccountFactory__factory, + AirdropDistributor__factory, ContractsRegister__factory, CreditManagerV2__factory, DieselToken__factory, @@ -25,6 +26,9 @@ export class GearboxContractFactory extends ContractFactory { accountFactory({ address, network }: ContractOpts) { return AccountFactory__factory.connect(address, this.appToolkit.getNetworkProvider(network)); } + airdropDistributor({ address, network }: ContractOpts) { + return AirdropDistributor__factory.connect(address, this.appToolkit.getNetworkProvider(network)); + } contractsRegister({ address, network }: ContractOpts) { return ContractsRegister__factory.connect(address, this.appToolkit.getNetworkProvider(network)); } @@ -43,6 +47,7 @@ export class GearboxContractFactory extends ContractFactory { } export type { AccountFactory } from './ethers'; +export type { AirdropDistributor } from './ethers'; export type { ContractsRegister } from './ethers'; export type { CreditManagerV2 } from './ethers'; export type { DieselToken } from './ethers'; diff --git a/src/apps/gearbox/ethereum/gearbox.rewards.contract-position-fetcher.ts b/src/apps/gearbox/ethereum/gearbox.rewards.contract-position-fetcher.ts new file mode 100644 index 000000000..2d260f0de --- /dev/null +++ b/src/apps/gearbox/ethereum/gearbox.rewards.contract-position-fetcher.ts @@ -0,0 +1,47 @@ +import { Inject } from '@nestjs/common'; +import { BigNumber, ethers } from 'ethers'; + +import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; +import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator'; +import { GetTokenBalancesParams } from '~position/template/contract-position.template.types'; +import { MerkleTemplateContractPositionFetcher } from '~position/template/merkle.template.contract-position-fetcher'; +import { Network } from '~types'; + +import { AirdropDistributor, GearboxContractFactory } from '../contracts'; + +import { EthereumGearboxRewardsMerkleCache, AIRDROP_DISTRIBUTOR, GEAR_TOKEN } from './gearbox.rewards.merkle-cache'; + +@PositionTemplate() +export class EthereumGearboxRewardsPositionFetcher extends MerkleTemplateContractPositionFetcher { + groupLabel = 'rewards'; + merkleAddress = AIRDROP_DISTRIBUTOR; + network = Network.ETHEREUM_MAINNET; + + isExcludedFromExplore = true; + isExcludedFromTvl = true; + + constructor( + @Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit, + @Inject(GearboxContractFactory) protected readonly contractFactory: GearboxContractFactory, + @Inject(EthereumGearboxRewardsMerkleCache) private readonly merkleCache: EthereumGearboxRewardsMerkleCache, + ) { + super(appToolkit); + } + + getContract(address: string): AirdropDistributor { + return this.contractFactory.airdropDistributor({ address, network: this.network }); + } + + async getRewardTokenAddresses() { + return [GEAR_TOKEN]; + } + + async getTokenBalancesPerPosition({ address, contract }: GetTokenBalancesParams) { + const rewardsData = await this.merkleCache.getClaim(GEAR_TOKEN, ethers.utils.getAddress(address)); + if (!rewardsData) return [0]; + + const { amount } = rewardsData; + const totalClaimed = await contract.claimed(address); + return [BigNumber.from(amount).sub(totalClaimed)]; + } +} diff --git a/src/apps/gearbox/ethereum/gearbox.rewards.merkle-cache.ts b/src/apps/gearbox/ethereum/gearbox.rewards.merkle-cache.ts new file mode 100644 index 000000000..ad8757374 --- /dev/null +++ b/src/apps/gearbox/ethereum/gearbox.rewards.merkle-cache.ts @@ -0,0 +1,73 @@ +import { Inject } from '@nestjs/common'; +import axios from 'axios'; +import _ from 'lodash'; + +import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; +import { MerkleCache } from '~position/template/merkle.cache'; +import { Network } from '~types'; + +import { GearboxContractFactory } from '../contracts'; + +type GearboxMerkleClaim = { + amount: string; + index: number; + proof: string[]; +}; + +type GearboxMerkleClaimResponse = { + merkleRoot: string; + toBlock: number; + tokenTotal: string; + claims: Record; +}; + +export const GEAR_TOKEN = '0xba3335588d9403515223f109edc4eb7269a9ab5d'; +export const AIRDROP_DISTRIBUTOR = '0xa7df60785e556d65292a2c9a077bb3a8fbf048bc'; + +export class EthereumGearboxRewardsMerkleCache extends MerkleCache { + appId = 'gearbox'; + groupId = 'rewards'; + network = Network.ETHEREUM_MAINNET; + + constructor( + @Inject(APP_TOOLKIT) appToolkit: IAppToolkit, + @Inject(GearboxContractFactory) private readonly contractFactory: GearboxContractFactory, + ) { + super(appToolkit); + } + + async resolveMerkleData(): Promise>> { + const allPermutations: string[] = []; + const allHexCharacters = '01233456789abcdef'; + for (let i = 0; i < allHexCharacters.length; i++) { + for (let j = 0; j < allHexCharacters.length; j++) { + allPermutations.push(allHexCharacters[i] + allHexCharacters[j]); + } + } + const airdropDistributorContract = this.contractFactory.airdropDistributor({ + address: AIRDROP_DISTRIBUTOR, + network: this.network, + }); + const merkleRoot = await airdropDistributorContract.merkleRoot(); + const urls = allPermutations.map( + p => + `https://raw.githubusercontent.com/Gearbox-protocol/rewards/master/merkle/mainnet_${merkleRoot.replace( + '0x', + '', + )}/${p}.json`, + ); + const allResponses = await Promise.all( + urls.map(url => + axios.get(url).then(r => { + const claimsData = r.data.claims; + return Object.fromEntries( + Object.entries(claimsData).map(([key, val]) => [key, { index: val.index, amount: val.amount }]), + ); + }), + ), + ); + const allData = _.merge({}, ...allResponses); + + return { [GEAR_TOKEN]: allData }; + } +} diff --git a/src/apps/gearbox/gearbox.module.ts b/src/apps/gearbox/gearbox.module.ts index d5c5fd77f..b21624aeb 100644 --- a/src/apps/gearbox/gearbox.module.ts +++ b/src/apps/gearbox/gearbox.module.ts @@ -6,13 +6,17 @@ import { GearboxContractFactory } from './contracts'; import { EthereumGearboxCreditAccountsContractPositionFetcher } from './ethereum/gearbox.credit-accounts.contract-position-fetcher'; import { EthereumGearboxLendingTokenFetcher } from './ethereum/gearbox.lending.token-fetcher'; import { EthereumGearboxPhantomTokenFetcher } from './ethereum/gearbox.phantom.token-fetcher'; +import { EthereumGearboxRewardsPositionFetcher } from './ethereum/gearbox.rewards.contract-position-fetcher'; +import { EthereumGearboxRewardsMerkleCache } from './ethereum/gearbox.rewards.merkle-cache'; @Module({ providers: [ - GearboxContractFactory, EthereumGearboxCreditAccountsContractPositionFetcher, EthereumGearboxLendingTokenFetcher, EthereumGearboxPhantomTokenFetcher, + EthereumGearboxRewardsPositionFetcher, + GearboxContractFactory, + EthereumGearboxRewardsMerkleCache, ], }) export class GearboxAppModule extends AbstractApp() {} diff --git a/src/network-provider/network-provider.registry.ts b/src/network-provider/network-provider.registry.ts index 98ea2c7cf..41a8b6518 100644 --- a/src/network-provider/network-provider.registry.ts +++ b/src/network-provider/network-provider.registry.ts @@ -5,7 +5,7 @@ export const DEFAULT_REGISTRY: Record, [Network.AVALANCHE_MAINNET]: 'https://avalanche.public-rpc.com', [Network.BINANCE_SMART_CHAIN_MAINNET]: 'https://bsc-dataseed.binance.org/', [Network.CELO_MAINNET]: 'https://forno.celo.org', - [Network.ETHEREUM_MAINNET]: 'https://eth-mainnet-public.unifra.io', + [Network.ETHEREUM_MAINNET]: 'https://eth.llamarpc.com', [Network.FANTOM_OPERA_MAINNET]: 'https://rpc.ftm.tools/', [Network.GNOSIS_MAINNET]: 'https://rpc.gnosischain.com/', [Network.HARMONY_MAINNET]: 'https://harmony.public-rpc.com',