From 6d6936fe952240021f1975e47cb44d169c6c1aee Mon Sep 17 00:00:00 2001 From: Dmitry Fesenko Date: Tue, 13 Jul 2021 17:06:57 +0300 Subject: [PATCH] feat(staking): expose staking API --- src/iam-client-lib.ts | 1 + src/iam/chainConfig.ts | 7 ++- src/staking/index.ts | 139 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/staking/index.ts diff --git a/src/iam-client-lib.ts b/src/iam-client-lib.ts index 4f311160..f233a31f 100644 --- a/src/iam-client-lib.ts +++ b/src/iam-client-lib.ts @@ -93,3 +93,4 @@ export { export { GnosisIam as SafeIam } from "./GnosisIam"; export * from "./utils/did"; +export * from "./staking"; diff --git a/src/iam/chainConfig.ts b/src/iam/chainConfig.ts index 6b55ee15..efae4164 100644 --- a/src/iam/chainConfig.ts +++ b/src/iam/chainConfig.ts @@ -5,7 +5,8 @@ import { VOLTA_PUBLIC_RESOLVER_ADDRESS, VOLTA_RESOLVER_V1_ADDRESS, VOLTA_IDENTITY_MANAGER_ADDRESS, - VOLTA_CLAIM_MANAGER_ADDRESS + VOLTA_CLAIM_MANAGER_ADDRESS, + VOLTA_STAKING_POOL_FACTORY_ADDRESS } from "@energyweb/iam-contracts"; import { CacheServerClientOptions } from "../cacheServerClient/cacheServerClient"; import { MessagingMethod } from "../utils/constants"; @@ -20,6 +21,7 @@ export interface ChainConfig { assetManagerAddress: string; didContractAddress: string; claimManagerAddress: string; + stakingPoolFactoryAddress: string; } export interface MessagingOptions { @@ -40,7 +42,8 @@ export const chainConfigs: Record = { domainNotifierAddress: VOLTA_DOMAIN_NOTIFER_ADDRESS, assetManagerAddress: VOLTA_IDENTITY_MANAGER_ADDRESS, didContractAddress: VoltaAddress1056, - claimManagerAddress: VOLTA_CLAIM_MANAGER_ADDRESS + claimManagerAddress: VOLTA_CLAIM_MANAGER_ADDRESS, + stakingPoolFactoryAddress: VOLTA_STAKING_POOL_FACTORY_ADDRESS } }; diff --git a/src/staking/index.ts b/src/staking/index.ts new file mode 100644 index 00000000..ccd3f96d --- /dev/null +++ b/src/staking/index.ts @@ -0,0 +1,139 @@ +import { DomainReader, StakingPoolFactory__factory, StakingPool__factory, } from "@energyweb/iam-contracts"; +import { StakingPool as StakingPoolContract } from "@energyweb/iam-contracts/dist/ethers-v4/StakingPool"; +import { StakingPoolFactory } from "@energyweb/iam-contracts/dist/ethers-v4/StakingPoolFactory"; +import { Signer, utils } from "ethers"; +import { chainConfigs } from "../iam/chainConfig"; + +const { namehash } = utils; + +export enum StakeStatus { NONSTAKING = 0, STAKING = 1, WITHDRAWING = 2 } + +export type Service = { + /** organization ENS name */ + org: string; + /** pool address */ + pool: string; + /** provider address */ + provider: string +} + +export type Stake = { + amount: utils.BigNumber; + start: utils.BigNumber; + withdrawalRequested: utils.BigNumber; + status: StakeStatus; +} + +export class StakingPoolService { + private constructor(private _stakingPoolFactory: StakingPoolFactory, private _domainReader: DomainReader, private _signer) { } + + static async init(signer: Required) { + const { chainId } = await signer.provider.getNetwork(); + const { stakingPoolFactoryAddress, ensRegistryAddress } = chainConfigs[chainId]; + const stakingPoolFactory = new StakingPoolFactory__factory(signer).attach(stakingPoolFactoryAddress); + const domainReader = new DomainReader({ ensRegistryAddress, provider: signer.provider }); + return new StakingPoolService(stakingPoolFactory, domainReader, signer); + } + + /** + * @description Deployes organization staking pool + * @emits StakingPoolFactory.StakingPoolLaunched + */ + async launchStakingPool( + { org, minStakingPeriod, patronRewardPortion, patronRoles, principal }: + { + /** organization ENS name */ + org: string, + /** minimum staking period in seconds */ + minStakingPeriod: number | utils.BigNumber, + /** patron's part of the reward in fractions of thousandth */ + patronRewardPortion: number, + /** roles required to stake */ + patronRoles: string[], + /** stake put by service provider when pool is launched */ + principal: utils.BigNumber + } + ): Promise { + (await this._stakingPoolFactory.launchStakingPool( + namehash(org), + minStakingPeriod, + patronRewardPortion, + patronRoles.map((r) => namehash(r)) + , + { value: principal.toHexString() } + )).wait(); + } + + + async allServices(): Promise { + const orgs = await this._stakingPoolFactory.orgsList(); + return Promise.all( + orgs.map( + (org) => this._stakingPoolFactory.services(org) + .then((service) => ({ ...service, org })) + .then((service) => this._domainReader.readName(service.org) + .then((org) => ({ ...service, org })) + ))); + } + + async getPool(org: string): Promise { + const service = await this._stakingPoolFactory.services(namehash(org)); + const pool = new StakingPool__factory(this._signer).attach(service.pool); + return new StakingPool(pool); + } +} + +export class StakingPool { + constructor(private pool: StakingPoolContract) { } + + /** + * @description Locks stake and starts accumulating reward + * @emits StakingPool.StakePut + */ + async putStake( + /** stake amount */ + stake: utils.BigNumber, + ): Promise { + (await this.pool.putStake({ + value: stake.toHexString() + })).wait(); + } + + /** + * + */ + async checkReward(): Promise { + return this.pool.checkReward(); + } + + /** + * + * @param patron + */ + async getStake(patron?: string): Promise { + if (!patron) { + patron = await this.pool.signer.getAddress(); + } + return this.pool.stakes(patron); + } + + /** + * @description Stops accumulating of the reward and prepars stake to withdraw after withdraw delay. + * Withdraw request unavailable until minimum staking period ends + */ + async requestWithdraw(): Promise { + (await this.pool.requestWithdraw()).wait(); + } + + /** + * @description pays back stake with accumulated reward. Withdrawn unavailable until withdrawn delay ends + * @emits StakingPool.StakeWithdrawn + */ + async withdraw(): Promise { + (await this.pool.withdraw()).wait(); + } + + connect(signer: Signer): StakingPool { + return new StakingPool(this.pool.connect(signer)); + } +}