From 9b0f315a6058fae333a01ccfbd43dcc1828f0485 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 12 Aug 2022 16:17:58 -0700 Subject: [PATCH] feat: add a list of staking contracts Signed-off-by: Raymond Feng --- .eslintignore | 2 + .prettierignore | 1 + README.md | 119 +++++++++++++++++++++- package.json | 3 +- src/__examples__/main.ts | 25 +++++ src/adapters/coco.adapter.ts | 32 ++++++ src/adapters/mtg.adapter.ts | 28 +++++ src/adapters/rirsu.adapter.ts | 24 +++++ src/adapters/robo.adapter.ts | 24 +++++ src/adapters/sky-farm.adapter.ts | 21 ++++ src/component.ts | 36 +++---- src/keys.ts | 18 ++-- src/services/staking-contracts.service.ts | 48 +++++++++ src/staking.ts | 10 ++ src/types.ts | 19 ---- 15 files changed, 365 insertions(+), 45 deletions(-) create mode 100644 src/__examples__/main.ts create mode 100644 src/adapters/coco.adapter.ts create mode 100644 src/adapters/mtg.adapter.ts create mode 100644 src/adapters/rirsu.adapter.ts create mode 100644 src/adapters/robo.adapter.ts create mode 100644 src/adapters/sky-farm.adapter.ts create mode 100644 src/services/staking-contracts.service.ts create mode 100644 src/staking.ts delete mode 100644 src/types.ts diff --git a/.eslintignore b/.eslintignore index cd8f69b..7cf4cfe 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,5 @@ node_modules/ dist/ coverage/ .eslintrc.js +src/types/ +hardhat.config.js diff --git a/.prettierignore b/.prettierignore index 1521c8b..2641e1c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ dist +src/types diff --git a/README.md b/README.md index 65af9ac..ae46f8c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,120 @@ -# collabland@/staking-contracts +# @collabland/staking-contracts This project provides support for a curated list of stacking contracts for Collab.Land's token gating capabilities. + +## Get started + +1. Install dependencies + + ```sh + npm install + ``` + +2. Run the build + + ```sh + npm run build + ``` + +3. Run the example + + ```sh + node dist/__examples__/main + ``` + +## Format code and check style + + ```sh + npm run lint + ``` + +or + + ```sh + npm run lint:fix + ``` + +## Add a new staking contract + +To add a new staking contract, please follow the steps below. + +1. Add the contract ABI json file, go to `src/contracts` and create a file such + as `my-abi.json` + +2. Run `npm run build` to generate TypeScript client code for the contract + +3. Add an adapter class to `src/adapters`: + +```ts +import {Provider} from '@ethersproject/abstract-provider'; +import {BindingScope, extensionFor, injectable} from '@loopback/core'; +import {BigNumber} from 'ethers'; +import {STAKING_ADAPTERS_EXTENSION_POINT} from '../keys'; +import {StackingContractAdapter} from '../staking'; +// Use the full path to import instead of `../types` +import {Coco__factory} from '../types/factories/Coco__factory'; + +@injectable( + { + scope: BindingScope.SINGLETON, // Mark the adapter as a singleton + }, + // Mark it as an extension to staking contracts service + extensionFor(STAKING_ADAPTERS_EXTENSION_POINT), +) +export class CocoStakingContractAdapter implements StackingContractAdapter { + /** + * The contract address + */ + contractAddress = '0x0Df016Fb18ef4195b2CF9d8623E236272ec52e14'; + + /** + * Get staked token ids for the given owner + * @param provider - Ethers provider + * @param owner - Owner address + * @returns + */ + getStakedTokenIds(provider: Provider, owner: string): Promise { + const contract = Coco__factory.connect(this.contractAddress, provider); + return contract.getStakes(owner); + } +} +``` + +4. Register the adapter class to `src/component.ts` + +```ts +// Copyright Abridged, Inc. 2022. All Rights Reserved. +// Node module: @collabland/staking-contracts +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + Component, + ContextTags, + injectable, + ServiceOrProviderClass, +} from '@loopback/core'; +import {CocoStakingContractAdapter} from './adapters/coco.adapter'; +import {MtgStakingContractAdapter} from './adapters/mtg.adapter'; +import {RirsuStakingContractAdapter} from './adapters/rirsu.adapter'; +import {RoboStakingContractAdapter} from './adapters/robo.adapter'; +import {SkyFarmContractAdapter} from './adapters/sky-farm.adapter'; +import {STAKING_CONTRACTS_COMPONENT} from './keys'; +import {StakingContractsService} from './services/staking-contracts.service'; + +// Configure the binding for StakingContractsComponent +@injectable({ + tags: {[ContextTags.KEY]: STAKING_CONTRACTS_COMPONENT}, +}) +export class StakingContractsComponent implements Component { + services: ServiceOrProviderClass[] = [ + StakingContractsService, + CocoStakingContractAdapter, + MtgStakingContractAdapter, + RirsuStakingContractAdapter, + RoboStakingContractAdapter, + SkyFarmContractAdapter, + ]; + constructor() {} +} +``` diff --git a/package.json b/package.json index c666958..08cbf32 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "README.md", "dist", "src", - "!*/__tests__" + "!*/__tests__", + "!*/__examples__" ], "peerDependencies": { "@loopback/core": "^4.0.2" diff --git a/src/__examples__/main.ts b/src/__examples__/main.ts new file mode 100644 index 0000000..8be4247 --- /dev/null +++ b/src/__examples__/main.ts @@ -0,0 +1,25 @@ +import {Application} from '@loopback/core'; +import {getDefaultProvider} from 'ethers'; +import {StakingContractsComponent} from '../component'; +import {STAKING_CONTRACTS_SERVICE} from '../keys'; + +async function main() { + const app = new Application(); + app.component(StakingContractsComponent); + const service = await app.get(STAKING_CONTRACTS_SERVICE); + const provider = getDefaultProvider(1); + const staked = await service.getStakedTokenIds( + provider, + '0x9abbf7218c65c4d22c8483b5d6be93075a3c159c', + ); + console.log(staked); + await app.stop(); + process.exit(0); +} + +if (require.main === module) { + main().catch(err => { + console.error(err); + process.exit(1); + }); +} diff --git a/src/adapters/coco.adapter.ts b/src/adapters/coco.adapter.ts new file mode 100644 index 0000000..7cf23ca --- /dev/null +++ b/src/adapters/coco.adapter.ts @@ -0,0 +1,32 @@ +import {Provider} from '@ethersproject/abstract-provider'; +import {BindingScope, extensionFor, injectable} from '@loopback/core'; +import {BigNumber} from 'ethers'; +import {STAKING_ADAPTERS_EXTENSION_POINT} from '../keys'; +import {StackingContractAdapter} from '../staking'; +// Use the full path to import instead of `../types` +import {Coco__factory} from '../types/factories/Coco__factory'; + +@injectable( + { + scope: BindingScope.SINGLETON, // Mark the adapter as a singleton + }, + // Mark it as an extension to staking contracts service + extensionFor(STAKING_ADAPTERS_EXTENSION_POINT), +) +export class CocoStakingContractAdapter implements StackingContractAdapter { + /** + * The contract address + */ + contractAddress = '0x0Df016Fb18ef4195b2CF9d8623E236272ec52e14'; + + /** + * Get staked token ids for the given owner + * @param provider - Ethers provider + * @param owner - Owner address + * @returns + */ + getStakedTokenIds(provider: Provider, owner: string): Promise { + const contract = Coco__factory.connect(this.contractAddress, provider); + return contract.getStakes(owner); + } +} diff --git a/src/adapters/mtg.adapter.ts b/src/adapters/mtg.adapter.ts new file mode 100644 index 0000000..fb80feb --- /dev/null +++ b/src/adapters/mtg.adapter.ts @@ -0,0 +1,28 @@ +import {Provider} from '@ethersproject/abstract-provider'; +import {BindingScope, extensionFor, injectable} from '@loopback/core'; +import {BigNumber} from 'ethers'; +import {STAKING_ADAPTERS_EXTENSION_POINT} from '../keys'; +import {StackingContractAdapter} from '../staking'; +import {MtgStaking__factory} from '../types/factories/MtgStaking__factory'; + +@injectable( + { + scope: BindingScope.SINGLETON, + }, + extensionFor(STAKING_ADAPTERS_EXTENSION_POINT), +) +export class MtgStakingContractAdapter implements StackingContractAdapter { + contractAddress = '0x2eb255a465c828837d6e8ba73072ec2c965dcf13'; + + async getStakedTokenIds( + provider: Provider, + owner: string, + ): Promise { + const contract = MtgStaking__factory.connect( + this.contractAddress, + provider, + ); + const records = await contract.getStakingRecords(owner); + return records[0]; + } +} diff --git a/src/adapters/rirsu.adapter.ts b/src/adapters/rirsu.adapter.ts new file mode 100644 index 0000000..79af676 --- /dev/null +++ b/src/adapters/rirsu.adapter.ts @@ -0,0 +1,24 @@ +import {Provider} from '@ethersproject/abstract-provider'; +import {BindingScope, extensionFor, injectable} from '@loopback/core'; +import {BigNumber} from 'ethers'; +import {STAKING_ADAPTERS_EXTENSION_POINT} from '../keys'; +import {StackingContractAdapter} from '../staking'; +import {RirsuStaking__factory} from '../types/factories/RirsuStaking__factory'; + +@injectable( + { + scope: BindingScope.SINGLETON, + }, + extensionFor(STAKING_ADAPTERS_EXTENSION_POINT), +) +export class RirsuStakingContractAdapter implements StackingContractAdapter { + contractAddress = '0x5dACC3a466fD9E39DCCB2fabE0852285a76a2c59'; + + getStakedTokenIds(provider: Provider, owner: string): Promise { + const contract = RirsuStaking__factory.connect( + this.contractAddress, + provider, + ); + return contract.stakedRiris(owner); + } +} diff --git a/src/adapters/robo.adapter.ts b/src/adapters/robo.adapter.ts new file mode 100644 index 0000000..06da8f6 --- /dev/null +++ b/src/adapters/robo.adapter.ts @@ -0,0 +1,24 @@ +import {Provider} from '@ethersproject/abstract-provider'; +import {BindingScope, extensionFor, injectable} from '@loopback/core'; +import {BigNumber} from 'ethers'; +import {STAKING_ADAPTERS_EXTENSION_POINT} from '../keys'; +import {StackingContractAdapter} from '../staking'; +import {RoboStaking__factory} from '../types/factories/RoboStaking__factory'; + +@injectable( + { + scope: BindingScope.SINGLETON, + }, + extensionFor(STAKING_ADAPTERS_EXTENSION_POINT), +) +export class RoboStakingContractAdapter implements StackingContractAdapter { + contractAddress = '0x5dACC3a466fD9E39DCCB2fabE0852285a76a2c59'; + + getStakedTokenIds(provider: Provider, owner: string): Promise { + const contract = RoboStaking__factory.connect( + this.contractAddress, + provider, + ); + return contract.getStaked(owner); + } +} diff --git a/src/adapters/sky-farm.adapter.ts b/src/adapters/sky-farm.adapter.ts new file mode 100644 index 0000000..16bd4b9 --- /dev/null +++ b/src/adapters/sky-farm.adapter.ts @@ -0,0 +1,21 @@ +import {Provider} from '@ethersproject/abstract-provider'; +import {BindingScope, extensionFor, injectable} from '@loopback/core'; +import {BigNumber} from 'ethers'; +import {STAKING_ADAPTERS_EXTENSION_POINT} from '../keys'; +import {StackingContractAdapter} from '../staking'; +import {SkyFarm__factory} from '../types/factories/SkyFarm__factory'; + +@injectable( + { + scope: BindingScope.SINGLETON, + }, + extensionFor(STAKING_ADAPTERS_EXTENSION_POINT), +) +export class SkyFarmContractAdapter implements StackingContractAdapter { + contractAddress = '0xc5933172228E273CF829672921290ca107611757'; + + getStakedTokenIds(provider: Provider, owner: string): Promise { + const contract = SkyFarm__factory.connect(this.contractAddress, provider); + return contract.getStakedIds(owner); + } +} diff --git a/src/component.ts b/src/component.ts index b4c6349..33a3e5a 100644 --- a/src/component.ts +++ b/src/component.ts @@ -4,29 +4,31 @@ // License text available at https://opensource.org/licenses/MIT import { - Application, - injectable, Component, - config, ContextTags, - CoreBindings, - inject, + injectable, + ServiceOrProviderClass, } from '@loopback/core'; -import {StakingContractsComponentBindings} from './keys'; -import { - DEFAULT_COLLABLAND_STAKING_OPTIONS, - StakingContractsComponentOptions, -} from './types'; +import {CocoStakingContractAdapter} from './adapters/coco.adapter'; +import {MtgStakingContractAdapter} from './adapters/mtg.adapter'; +import {RirsuStakingContractAdapter} from './adapters/rirsu.adapter'; +import {RoboStakingContractAdapter} from './adapters/robo.adapter'; +import {SkyFarmContractAdapter} from './adapters/sky-farm.adapter'; +import {STAKING_CONTRACTS_COMPONENT} from './keys'; +import {StakingContractsService} from './services/staking-contracts.service'; // Configure the binding for StakingContractsComponent @injectable({ - tags: {[ContextTags.KEY]: StakingContractsComponentBindings.COMPONENT}, + tags: {[ContextTags.KEY]: STAKING_CONTRACTS_COMPONENT}, }) export class StakingContractsComponent implements Component { - constructor( - @inject(CoreBindings.APPLICATION_INSTANCE) - private application: Application, - @config() - private options: StakingContractsComponentOptions = DEFAULT_COLLABLAND_STAKING_OPTIONS, - ) {} + services: ServiceOrProviderClass[] = [ + StakingContractsService, + CocoStakingContractAdapter, + MtgStakingContractAdapter, + RirsuStakingContractAdapter, + RoboStakingContractAdapter, + SkyFarmContractAdapter, + ]; + constructor() {} } diff --git a/src/keys.ts b/src/keys.ts index 0a23851..bed2b50 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -4,13 +4,17 @@ // License text available at https://opensource.org/licenses/MIT import {BindingKey, CoreBindings} from '@loopback/core'; -import {StakingContractsComponent} from './component'; +import type {StakingContractsComponent} from './component'; +import type {StakingContractsService} from './services/staking-contracts.service'; -/** - * Binding keys used by this component. - */ -export namespace StakingContractsComponentBindings { - export const COMPONENT = BindingKey.create( +export const STAKING_CONTRACTS_COMPONENT = + BindingKey.create( `${CoreBindings.COMPONENTS}.StakingContractsComponent`, ); -} + +export const STAKING_CONTRACTS_SERVICE = + BindingKey.create( + 'services.StakingContractsService', + ); + +export const STAKING_ADAPTERS_EXTENSION_POINT = 'collabland.stakingContracts'; diff --git a/src/services/staking-contracts.service.ts b/src/services/staking-contracts.service.ts new file mode 100644 index 0000000..6d8da2e --- /dev/null +++ b/src/services/staking-contracts.service.ts @@ -0,0 +1,48 @@ +import { + BindingScope, + ContextTags, + extensions, + injectable, +} from '@loopback/core'; +import {BigNumber, providers} from 'ethers'; +import { + STAKING_ADAPTERS_EXTENSION_POINT, + STAKING_CONTRACTS_SERVICE, +} from '../keys'; +import {StackingContractAdapter} from '../staking'; + +@injectable({ + scope: BindingScope.SINGLETON, + tags: { + [ContextTags.KEY]: STAKING_CONTRACTS_SERVICE, + }, +}) +export class StakingContractsService { + constructor( + @extensions.list(STAKING_ADAPTERS_EXTENSION_POINT) + private adapters: StackingContractAdapter[], + ) {} + + async getStakedTokenIds( + provider: providers.Provider, + owner: string, + ...contractAddresses: string[] + ) { + const tokens: Record = {}; + if (contractAddresses.length === 0) { + contractAddresses = this.adapters.map(a => a.contractAddress); + } + for (const address of contractAddresses) { + const adapter = this.adapters.find(a => a.contractAddress === address); + if (adapter != null) { + try { + const ids = await adapter.getStakedTokenIds(provider, owner); + tokens[address] = ids; + } catch (err) { + // Ignore + } + } + } + return tokens; + } +} diff --git a/src/staking.ts b/src/staking.ts new file mode 100644 index 0000000..a3882d2 --- /dev/null +++ b/src/staking.ts @@ -0,0 +1,10 @@ +import {BigNumber, providers} from 'ethers'; + +export interface StackingContractAdapter { + contractAddress: string; + + getStakedTokenIds( + provider: providers.Provider, + owner: string, + ): Promise; +} diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 9ff6c37..0000000 --- a/src/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Abridged, Inc. 2022. All Rights Reserved. -// Node module: @collabland/staking-contracts -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -/** - * Interface defining the component's options object - */ -export interface StakingContractsComponentOptions { - // Add the definitions here -} - -/** - * Default options for the component - */ -export const DEFAULT_COLLABLAND_STAKING_OPTIONS: StakingContractsComponentOptions = - { - // Specify the values here - };