diff --git a/src/apps/pool-together-v5/assets/logo.png b/src/apps/pool-together-v5/assets/logo.png new file mode 100644 index 000000000..e7836228e Binary files /dev/null and b/src/apps/pool-together-v5/assets/logo.png differ diff --git a/src/apps/pool-together-v5/common/pool-together-v5.prize-vault.token-fetcher.ts b/src/apps/pool-together-v5/common/pool-together-v5.prize-vault.token-fetcher.ts new file mode 100644 index 000000000..fef45a958 --- /dev/null +++ b/src/apps/pool-together-v5/common/pool-together-v5.prize-vault.token-fetcher.ts @@ -0,0 +1,84 @@ +import { Inject } from '@nestjs/common'; + +import { Erc4626 } from '~contract/contracts'; +import { AppTokenTemplatePositionFetcher } from '~position/template/app-token.template.position-fetcher'; +import { DefaultAppTokenDefinition, GetAddressesParams, GetDataPropsParams, GetDefinitionsParams, GetDisplayPropsParams, GetPricePerShareParams, GetUnderlyingTokensParams } from '~position/template/app-token.template.types'; +import { PRIZE_VAULT_FACTORY_ADDRESSES } from './pool-together.v5.constants'; +import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; +import { PoolTogetherV5ContractFactory } from '../contracts'; + +export abstract class PoolTogetherV5PrizeVaultTokenFetcher extends AppTokenTemplatePositionFetcher { + groupLabel: string; + + constructor( + @Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit, + @Inject(PoolTogetherV5ContractFactory) private readonly contractFactory: PoolTogetherV5ContractFactory + ) { + super(appToolkit); + } + + getContract(address: string): Erc4626 { + return this.appToolkit.globalContracts.erc4626({ address, network: this.network }); + } + + async getAddresses({ definitions }: GetAddressesParams): Promise { + return definitions.map(def => def.address); + } + + async getDefinitions(params: GetDefinitionsParams): Promise { + const { multicall } = params; + + const vaultFactoryAddress = PRIZE_VAULT_FACTORY_ADDRESSES[this.network]; + + if(!!vaultFactoryAddress) { + const vaultFactory = multicall.wrap( + this.contractFactory.poolTogetherV5VaultFactory( + { network: this.network, address: vaultFactoryAddress } + ) + ); + + const totalVaults = Number(await vaultFactory.totalVaults()); + const vaultIndexes = [...Array(totalVaults).keys()]; + + const vaultAddresses = await Promise.all(vaultIndexes.map(index => vaultFactory.allVaults(index))); + + return vaultAddresses.map(address => ({ address })); + } else { + return [] + } + } + + async getUnderlyingTokenDefinitions({ contract }: GetUnderlyingTokensParams) { + const underlyingToken = await contract.asset(); + return [{ address: underlyingToken, network: this.network }]; + } + + async getPricePerShare({ contract, appToken }: GetPricePerShareParams) { + const oneShare = BigInt(10 ** appToken.tokens[0].decimals); + const exchangeRate = (await contract.convertToAssets(oneShare)).toBigInt(); + const pricePerShare = Number(exchangeRate / oneShare); + return [pricePerShare]; + } + + async getLiquidity({ contract, appToken }: GetDataPropsParams) { + const reserveRaw = await contract.totalAssets(); + const reserve = Number(reserveRaw) / 10 ** appToken.tokens[0].decimals; + const liquidity = reserve * appToken.tokens[0].price; + return liquidity; + } + + async getReserves({ contract, appToken }: GetDataPropsParams) { + const reserveRaw = await contract.totalAssets(); + const reserve = Number(reserveRaw) / 10 ** appToken.tokens[0].decimals; + return [reserve]; + } + + async getLabel({ contract }: GetDisplayPropsParams) { + return contract.name(); + } + + async getLabelDetailed({ appToken }: GetDisplayPropsParams) { + return appToken.symbol; + } + +} diff --git a/src/apps/pool-together-v5/common/pool-together.v5.constants.ts b/src/apps/pool-together-v5/common/pool-together.v5.constants.ts new file mode 100644 index 000000000..e931e51b9 --- /dev/null +++ b/src/apps/pool-together-v5/common/pool-together.v5.constants.ts @@ -0,0 +1,5 @@ +import { Network } from "~types" + +export const PRIZE_VAULT_FACTORY_ADDRESSES: Partial> = { + [Network.OPTIMISM_MAINNET]: '0xF65FA202907D6046D1eF33C521889B54BdE08081' +} \ No newline at end of file diff --git a/src/apps/pool-together-v5/contracts/abis/pool-together-v-5-vault-factory.json b/src/apps/pool-together-v5/contracts/abis/pool-together-v-5-vault-factory.json new file mode 100644 index 000000000..b387d3773 --- /dev/null +++ b/src/apps/pool-together-v5/contracts/abis/pool-together-v-5-vault-factory.json @@ -0,0 +1,56 @@ +[ + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "contract Vault", "name": "vault", "type": "address" }, + { "indexed": true, "internalType": "contract VaultFactory", "name": "vaultFactory", "type": "address" } + ], + "name": "NewFactoryVault", + "type": "event" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "allVaults", + "outputs": [{ "internalType": "contract Vault", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "contract IERC20", "name": "_asset", "type": "address" }, + { "internalType": "string", "name": "_name", "type": "string" }, + { "internalType": "string", "name": "_symbol", "type": "string" }, + { "internalType": "contract IERC4626", "name": "_yieldVault", "type": "address" }, + { "internalType": "contract PrizePool", "name": "_prizePool", "type": "address" }, + { "internalType": "address", "name": "_claimer", "type": "address" }, + { "internalType": "address", "name": "_yieldFeeRecipient", "type": "address" }, + { "internalType": "uint32", "name": "_yieldFeePercentage", "type": "uint32" }, + { "internalType": "address", "name": "_owner", "type": "address" } + ], + "name": "deployVault", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "contract Vault", "name": "", "type": "address" }], + "name": "deployedVaults", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "deployerNonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalVaults", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/apps/pool-together-v5/contracts/ethers/PoolTogetherV5VaultFactory.ts b/src/apps/pool-together-v5/contracts/ethers/PoolTogetherV5VaultFactory.ts new file mode 100644 index 000000000..fe74d4af7 --- /dev/null +++ b/src/apps/pool-together-v5/contracts/ethers/PoolTogetherV5VaultFactory.ts @@ -0,0 +1,218 @@ +/* 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 interface PoolTogetherV5VaultFactoryInterface extends utils.Interface { + functions: { + 'allVaults(uint256)': FunctionFragment; + 'deployVault(address,string,string,address,address,address,address,uint32,address)': FunctionFragment; + 'deployedVaults(address)': FunctionFragment; + 'deployerNonces(address)': FunctionFragment; + 'totalVaults()': FunctionFragment; + }; + + getFunction( + nameOrSignatureOrTopic: 'allVaults' | 'deployVault' | 'deployedVaults' | 'deployerNonces' | 'totalVaults', + ): FunctionFragment; + + encodeFunctionData(functionFragment: 'allVaults', values: [PromiseOrValue]): string; + encodeFunctionData( + functionFragment: 'deployVault', + values: [ + PromiseOrValue, + PromiseOrValue, + PromiseOrValue, + PromiseOrValue, + PromiseOrValue, + PromiseOrValue, + PromiseOrValue, + PromiseOrValue, + PromiseOrValue, + ], + ): string; + encodeFunctionData(functionFragment: 'deployedVaults', values: [PromiseOrValue]): string; + encodeFunctionData(functionFragment: 'deployerNonces', values: [PromiseOrValue]): string; + encodeFunctionData(functionFragment: 'totalVaults', values?: undefined): string; + + decodeFunctionResult(functionFragment: 'allVaults', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'deployVault', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'deployedVaults', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'deployerNonces', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'totalVaults', data: BytesLike): Result; + + events: { + 'NewFactoryVault(address,address)': EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: 'NewFactoryVault'): EventFragment; +} + +export interface NewFactoryVaultEventObject { + vault: string; + vaultFactory: string; +} +export type NewFactoryVaultEvent = TypedEvent<[string, string], NewFactoryVaultEventObject>; + +export type NewFactoryVaultEventFilter = TypedEventFilter; + +export interface PoolTogetherV5VaultFactory extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: PoolTogetherV5VaultFactoryInterface; + + 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: { + allVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise<[string]>; + + deployVault( + _asset: PromiseOrValue, + _name: PromiseOrValue, + _symbol: PromiseOrValue, + _yieldVault: PromiseOrValue, + _prizePool: PromiseOrValue, + _claimer: PromiseOrValue, + _yieldFeeRecipient: PromiseOrValue, + _yieldFeePercentage: PromiseOrValue, + _owner: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + deployedVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise<[boolean]>; + + deployerNonces(arg0: PromiseOrValue, overrides?: CallOverrides): Promise<[BigNumber]>; + + totalVaults(overrides?: CallOverrides): Promise<[BigNumber]>; + }; + + allVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + deployVault( + _asset: PromiseOrValue, + _name: PromiseOrValue, + _symbol: PromiseOrValue, + _yieldVault: PromiseOrValue, + _prizePool: PromiseOrValue, + _claimer: PromiseOrValue, + _yieldFeeRecipient: PromiseOrValue, + _yieldFeePercentage: PromiseOrValue, + _owner: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + deployedVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + deployerNonces(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + totalVaults(overrides?: CallOverrides): Promise; + + callStatic: { + allVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + deployVault( + _asset: PromiseOrValue, + _name: PromiseOrValue, + _symbol: PromiseOrValue, + _yieldVault: PromiseOrValue, + _prizePool: PromiseOrValue, + _claimer: PromiseOrValue, + _yieldFeeRecipient: PromiseOrValue, + _yieldFeePercentage: PromiseOrValue, + _owner: PromiseOrValue, + overrides?: CallOverrides, + ): Promise; + + deployedVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + deployerNonces(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + totalVaults(overrides?: CallOverrides): Promise; + }; + + filters: { + 'NewFactoryVault(address,address)'( + vault?: PromiseOrValue | null, + vaultFactory?: PromiseOrValue | null, + ): NewFactoryVaultEventFilter; + NewFactoryVault( + vault?: PromiseOrValue | null, + vaultFactory?: PromiseOrValue | null, + ): NewFactoryVaultEventFilter; + }; + + estimateGas: { + allVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + deployVault( + _asset: PromiseOrValue, + _name: PromiseOrValue, + _symbol: PromiseOrValue, + _yieldVault: PromiseOrValue, + _prizePool: PromiseOrValue, + _claimer: PromiseOrValue, + _yieldFeeRecipient: PromiseOrValue, + _yieldFeePercentage: PromiseOrValue, + _owner: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + deployedVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + deployerNonces(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + totalVaults(overrides?: CallOverrides): Promise; + }; + + populateTransaction: { + allVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + deployVault( + _asset: PromiseOrValue, + _name: PromiseOrValue, + _symbol: PromiseOrValue, + _yieldVault: PromiseOrValue, + _prizePool: PromiseOrValue, + _claimer: PromiseOrValue, + _yieldFeeRecipient: PromiseOrValue, + _yieldFeePercentage: PromiseOrValue, + _owner: PromiseOrValue, + overrides?: Overrides & { from?: PromiseOrValue }, + ): Promise; + + deployedVaults(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + deployerNonces(arg0: PromiseOrValue, overrides?: CallOverrides): Promise; + + totalVaults(overrides?: CallOverrides): Promise; + }; +} diff --git a/src/apps/pool-together-v5/contracts/ethers/common.ts b/src/apps/pool-together-v5/contracts/ethers/common.ts new file mode 100644 index 000000000..35f31be99 --- /dev/null +++ b/src/apps/pool-together-v5/contracts/ethers/common.ts @@ -0,0 +1,32 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { Listener } from '@ethersproject/providers'; +import type { Event, EventFilter } from 'ethers'; + +export interface TypedEvent = any, TArgsObject = any> extends Event { + args: TArgsArray & TArgsObject; +} + +export interface TypedEventFilter<_TEvent extends TypedEvent> extends EventFilter {} + +export interface TypedListener { + (...listenerArg: [...__TypechainArgsArray, TEvent]): void; +} + +type __TypechainArgsArray = T extends TypedEvent ? U : never; + +export interface OnEvent { + (eventFilter: TypedEventFilter, listener: TypedListener): TRes; + (eventName: string, listener: Listener): TRes; +} + +export type MinEthersFactory = { + deploy(...a: ARGS[]): Promise; +}; + +export type GetContractTypeFromFactory = F extends MinEthersFactory ? C : never; + +export type GetARGsTypeFromFactory = F extends MinEthersFactory ? Parameters : never; + +export type PromiseOrValue = T | Promise; diff --git a/src/apps/pool-together-v5/contracts/ethers/factories/PoolTogetherV5VaultFactory__factory.ts b/src/apps/pool-together-v5/contracts/ethers/factories/PoolTogetherV5VaultFactory__factory.ts new file mode 100644 index 000000000..710dea67f --- /dev/null +++ b/src/apps/pool-together-v5/contracts/ethers/factories/PoolTogetherV5VaultFactory__factory.ts @@ -0,0 +1,168 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Signer, utils } from 'ethers'; +import type { Provider } from '@ethersproject/providers'; +import type { PoolTogetherV5VaultFactory, PoolTogetherV5VaultFactoryInterface } from '../PoolTogetherV5VaultFactory'; + +const _abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'contract Vault', + name: 'vault', + type: 'address', + }, + { + indexed: true, + internalType: 'contract VaultFactory', + name: 'vaultFactory', + type: 'address', + }, + ], + name: 'NewFactoryVault', + type: 'event', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'allVaults', + outputs: [ + { + internalType: 'contract Vault', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: '_asset', + type: 'address', + }, + { + internalType: 'string', + name: '_name', + type: 'string', + }, + { + internalType: 'string', + name: '_symbol', + type: 'string', + }, + { + internalType: 'contract IERC4626', + name: '_yieldVault', + type: 'address', + }, + { + internalType: 'contract PrizePool', + name: '_prizePool', + type: 'address', + }, + { + internalType: 'address', + name: '_claimer', + type: 'address', + }, + { + internalType: 'address', + name: '_yieldFeeRecipient', + type: 'address', + }, + { + internalType: 'uint32', + name: '_yieldFeePercentage', + type: 'uint32', + }, + { + internalType: 'address', + name: '_owner', + type: 'address', + }, + ], + name: 'deployVault', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract Vault', + name: '', + type: 'address', + }, + ], + name: 'deployedVaults', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'deployerNonces', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalVaults', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +export class PoolTogetherV5VaultFactory__factory { + static readonly abi = _abi; + static createInterface(): PoolTogetherV5VaultFactoryInterface { + return new utils.Interface(_abi) as PoolTogetherV5VaultFactoryInterface; + } + static connect(address: string, signerOrProvider: Signer | Provider): PoolTogetherV5VaultFactory { + return new Contract(address, _abi, signerOrProvider) as PoolTogetherV5VaultFactory; + } +} diff --git a/src/apps/pool-together-v5/contracts/ethers/factories/index.ts b/src/apps/pool-together-v5/contracts/ethers/factories/index.ts new file mode 100644 index 000000000..4ca168bd6 --- /dev/null +++ b/src/apps/pool-together-v5/contracts/ethers/factories/index.ts @@ -0,0 +1,4 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export { PoolTogetherV5VaultFactory__factory } from './PoolTogetherV5VaultFactory__factory'; diff --git a/src/apps/pool-together-v5/contracts/ethers/index.ts b/src/apps/pool-together-v5/contracts/ethers/index.ts new file mode 100644 index 000000000..5b35a10e7 --- /dev/null +++ b/src/apps/pool-together-v5/contracts/ethers/index.ts @@ -0,0 +1,6 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export type { PoolTogetherV5VaultFactory } from './PoolTogetherV5VaultFactory'; +export * as factories from './factories'; +export { PoolTogetherV5VaultFactory__factory } from './factories/PoolTogetherV5VaultFactory__factory'; diff --git a/src/apps/pool-together-v5/contracts/index.ts b/src/apps/pool-together-v5/contracts/index.ts new file mode 100644 index 000000000..daf7a59d9 --- /dev/null +++ b/src/apps/pool-together-v5/contracts/index.ts @@ -0,0 +1,23 @@ +import { Injectable, Inject } from '@nestjs/common'; + +import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; +import { ContractFactory } from '~contract/contracts'; +import { Network } from '~types/network.interface'; + +import { PoolTogetherV5VaultFactory__factory } from './ethers'; + +// eslint-disable-next-line +type ContractOpts = { address: string; network: Network }; + +@Injectable() +export class PoolTogetherV5ContractFactory extends ContractFactory { + constructor(@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit) { + super((network: Network) => appToolkit.getNetworkProvider(network)); + } + + poolTogetherV5VaultFactory({ address, network }: ContractOpts) { + return PoolTogetherV5VaultFactory__factory.connect(address, this.appToolkit.getNetworkProvider(network)); + } +} + +export type { PoolTogetherV5VaultFactory } from './ethers'; diff --git a/src/apps/pool-together-v5/optimism/pool-together-v5.prize-vault.token-fetcher.ts b/src/apps/pool-together-v5/optimism/pool-together-v5.prize-vault.token-fetcher.ts new file mode 100644 index 000000000..d7e84fc1f --- /dev/null +++ b/src/apps/pool-together-v5/optimism/pool-together-v5.prize-vault.token-fetcher.ts @@ -0,0 +1,7 @@ +import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator'; +import { PoolTogetherV5PrizeVaultTokenFetcher } from '../common/pool-together-v5.prize-vault.token-fetcher'; + +@PositionTemplate() +export class OptimismPoolTogetherV5PrizeVaultTokenFetcher extends PoolTogetherV5PrizeVaultTokenFetcher { + groupLabel: 'Prize Vaults'; +} diff --git a/src/apps/pool-together-v5/pool-together-v5.module.ts b/src/apps/pool-together-v5/pool-together-v5.module.ts new file mode 100644 index 000000000..859cb0a7e --- /dev/null +++ b/src/apps/pool-together-v5/pool-together-v5.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; + +import { AbstractApp } from '~app/app.dynamic-module'; +import { OptimismPoolTogetherV5PrizeVaultTokenFetcher } from './optimism/pool-together-v5.prize-vault.token-fetcher'; +import { PoolTogetherV5ContractFactory } from './contracts'; + +@Module({ + providers: [ + OptimismPoolTogetherV5PrizeVaultTokenFetcher, + PoolTogetherV5ContractFactory + ], +}) +export class PoolTogetherV5AppModule extends AbstractApp() {}