Skip to content

Commit

Permalink
Merge pull request #71 from credbull/restructure-contracts
Browse files Browse the repository at this point in the history
Re-structuring and renaming of contracts code.
  • Loading branch information
jplodge-pro authored Jul 5, 2024
2 parents 3d984f5 + 41cf88b commit 13eec92
Show file tree
Hide file tree
Showing 86 changed files with 1,569 additions and 1,419 deletions.
2 changes: 1 addition & 1 deletion packages/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './src/modules/accounts/accounts.dto';
export * from './src/modules/accounts/kyc.dto';
export * from './src/modules/accounts/whiteList.dto';
export * from './src/modules/accounts/wallets.dto';
export * from './src/types/supabase';
export * from './src/types/responses';
22 changes: 11 additions & 11 deletions packages/api/src/modules/accounts/accounts.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CredbullKYCProvider } from '@credbull/contracts';
import { CredbullWhiteListProvider } from '@credbull/contracts';
import { Test, TestingModule } from '@nestjs/testing';
import { SupabaseClient } from '@supabase/supabase-js';
import { beforeEach, describe, expect, it, vi } from 'vitest';
Expand All @@ -11,22 +11,22 @@ import { ConfigurationModule } from '../../utils/module';

import { AccountsController } from './accounts.controller';
import { AccountsModule } from './accounts.module';
import { KYCStatus } from './kyc.dto';
import { KycService } from './kyc.service';
import { WhiteListStatus } from './whiteList.dto';
import { WhiteListService } from './whiteList.service';

describe('AccountsController', () => {
let controller: AccountsController;
let client: DeepMockProxy<SupabaseClient>;
let admin: DeepMockProxy<SupabaseClient>;
let kyc: DeepMockProxy<CredbullKYCProvider>;
let whiteList: DeepMockProxy<CredbullWhiteListProvider>;
let ethers: DeepMockProxy<EthersService>;
beforeEach(async () => {
client = mockDeep<SupabaseClient>();
admin = mockDeep<SupabaseClient>();
kyc = mockDeep<CredbullKYCProvider>();
whiteList = mockDeep<CredbullWhiteListProvider>();
ethers = mockDeep<EthersService>();

(KycService.prototype as any).getOnChainProvider = vi.fn().mockReturnValue(kyc);
(WhiteListService.prototype as any).getOnChainProvider = vi.fn().mockReturnValue(whiteList);

const service = { client: () => client, admin: () => admin };

Expand All @@ -44,7 +44,7 @@ describe('AccountsController', () => {
controller = await module.resolve<AccountsController>(AccountsController);
});

it('should return pending status if there is no kyc event', async () => {
it('should return pending status if there is no white list event', async () => {
const select = vi.fn();
const eq = vi.fn();
const single = vi.fn();
Expand All @@ -57,7 +57,7 @@ describe('AccountsController', () => {

const { status } = await controller.status();

expect(status).toBe(KYCStatus.PENDING);
expect(status).toBe(WhiteListStatus.PENDING);
});

it('should whitelist an existing account', async () => {
Expand Down Expand Up @@ -91,13 +91,13 @@ describe('AccountsController', () => {
admin.from.mockReturnValue({ select, insert } as any);
ethers.operator.mockResolvedValue({} as any);

kyc.status.mockResolvedValueOnce(false);
kyc.updateStatus.mockResolvedValueOnce({} as any);
whiteList.status.mockResolvedValueOnce(false);
whiteList.updateStatus.mockResolvedValueOnce({} as any);

const { status } = await controller.whitelist({ user_id, address });

expect(insert.mock.calls[0][0]).toStrictEqual({ address, user_id, event_name: 'accepted' });
expect(status).toBe(KYCStatus.ACTIVE);
expect(status).toBe(WhiteListStatus.ACTIVE);
});

it('should verify the signature and link the wallet', async () => {
Expand Down
14 changes: 7 additions & 7 deletions packages/api/src/modules/accounts/accounts.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ import { UserWalletDto } from '../../types/db.dto';
import { isKnownError } from '../../utils/errors';

import { AccountStatusDto } from './accounts.dto';
import { KYCStatus, WhitelistAccountDto } from './kyc.dto';
import { KycService } from './kyc.service';
import { WalletDto } from './wallets.dto';
import { WalletsService } from './wallets.service';
import { WhiteListAccountDto, WhiteListStatus } from './whiteList.dto';
import { WhiteListService } from './whiteList.service';

@Controller('accounts')
@ApiBearerAuth()
@UseGuards(SupabaseGuard)
@ApiTags('Accounts')
export class AccountsController {
constructor(
private readonly kyc: KycService,
private readonly whiteList: WhiteListService,
private readonly wallets: WalletsService,
) {}

Expand All @@ -36,7 +36,7 @@ export class AccountsController {
@ApiResponse({ status: 400, description: 'Bad Request' })
@ApiResponse({ status: 500, description: 'Internal Error' })
async status(): Promise<AccountStatusDto> {
const { data, error } = await this.kyc.status();
const { data, error } = await this.whiteList.status();

if (isKnownError(error)) throw new BadRequestException(error);
if (error) throw new InternalServerErrorException(error);
Expand All @@ -51,14 +51,14 @@ export class AccountsController {
@ApiResponse({ status: 200, description: 'Success', type: AccountStatusDto })
@ApiResponse({ status: 400, description: 'Bad Request' })
@ApiResponse({ status: 500, description: 'Internal Error' })
async whitelist(@Body() dto: WhitelistAccountDto): Promise<AccountStatusDto> {
const { data, error } = await this.kyc.whitelist(dto);
async whitelist(@Body() dto: WhiteListAccountDto): Promise<AccountStatusDto> {
const { data, error } = await this.whiteList.whitelist(dto);

if (isKnownError(error)) throw new BadRequestException(error);
if (error) throw new InternalServerErrorException(error);
if (!data) throw new NotFoundException();

return new AccountStatusDto({ status: KYCStatus.ACTIVE });
return new AccountStatusDto({ status: WhiteListStatus.ACTIVE });
}

@Post('link-wallet')
Expand Down
12 changes: 6 additions & 6 deletions packages/api/src/modules/accounts/accounts.dto.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum } from 'class-validator';

import { KYCStatus } from './kyc.dto';
import { WhiteListStatus } from './whiteList.dto';

export class AccountStatusDto {
@IsEnum(KYCStatus)
@IsEnum(WhiteListStatus)
@ApiProperty({
example: 'active',
description: 'account kyc status',
enum: KYCStatus,
enumName: 'KYCStatus',
description: 'account white list status',
enum: WhiteListStatus,
enumName: 'WhiteListStatus',
})
status: KYCStatus;
status: WhiteListStatus;

constructor(partial: Partial<AccountStatusDto>) {
Object.assign(this, partial);
Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/modules/accounts/accounts.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { VaultsModule } from '../vaults/vaults.module';
import { VaultsService } from '../vaults/vaults.service';

import { AccountsController } from './accounts.controller';
import { KycService } from './kyc.service';
import { WalletsService } from './wallets.service';
import { WhiteListService } from './whiteList.service';

@Module({
imports: [ConfigurationModule, SupabaseModule, EthersModule, VaultsModule],
providers: [KycService, WalletsService, VaultsService],
providers: [WhiteListService, WalletsService, VaultsService],
controllers: [AccountsController],
exports: [KycService, WalletsService],
exports: [WhiteListService, WalletsService],
})
export class AccountsModule {}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';

export enum KYCStatus {
export enum WhiteListStatus {
ACTIVE = 'active',
PENDING = 'pending',
REJECTED = 'rejected',
}

export class WhitelistAccountDto {
export class WhiteListAccountDto {
@IsString()
@ApiProperty({
example: '0x0000000',
Expand All @@ -21,7 +21,7 @@ export class WhitelistAccountDto {
})
user_id: string;

constructor(partial: Partial<WhitelistAccountDto>) {
constructor(partial: Partial<WhiteListAccountDto>) {
Object.assign(this, partial);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CredbullKYCProvider__factory } from '@credbull/contracts';
import { CredbullWhiteListProvider__factory } from '@credbull/contracts';
import { Injectable } from '@nestjs/common';
import * as _ from 'lodash';

Expand All @@ -9,36 +9,36 @@ import { ServiceResponse } from '../../types/responses';
import { Tables } from '../../types/supabase';
import { responseFromRead, responseFromWrite } from '../../utils/contracts';

import { KYCStatus, WhitelistAccountDto } from './kyc.dto';
import { WhiteListAccountDto, WhiteListStatus } from './whiteList.dto';

@Injectable()
export class KycService {
export class WhiteListService {
constructor(
private readonly ethers: EthersService,
private readonly supabase: SupabaseService,
private readonly supabaseAdmin: SupabaseAdminService,
) {}

async status(): Promise<ServiceResponse<KYCStatus>> {
async status(): Promise<ServiceResponse<WhiteListStatus>> {
const client = this.supabase.client();

const events = await client.from('kyc_events').select().eq('event_name', 'accepted').single();
const events = await client.from('whitelist_events').select().eq('event_name', 'accepted').single();
if (events.error) return events;

if (!events.data?.address) return { data: KYCStatus.PENDING };
if (!events.data?.address) return { data: WhiteListStatus.PENDING };

const kycProvider = await client.from('vault_entities').select('*').eq('type', 'kyc_provider');
if (kycProvider.error) return kycProvider;
const whiteListProvider = await client.from('vault_entities').select('*').eq('type', 'whitelist_provider');
if (whiteListProvider.error) return whiteListProvider;

const distinctProviders = _.uniqBy(kycProvider.data ?? [], 'address');
const distinctProviders = _.uniqBy(whiteListProvider.data ?? [], 'address');

const check = await this.checkOnChain(distinctProviders, events.data?.address);
if (check.error) return check;

return check.data ? { data: KYCStatus.ACTIVE } : { data: KYCStatus.REJECTED };
return check.data ? { data: WhiteListStatus.ACTIVE } : { data: WhiteListStatus.REJECTED };
}

async whitelist(dto: WhitelistAccountDto): Promise<ServiceResponse<Tables<'kyc_events'>[]>> {
async whitelist(dto: WhiteListAccountDto): Promise<ServiceResponse<Tables<'whitelist_events'>[]>> {
const admin = this.supabaseAdmin.admin();

const wallet = await admin
Expand All @@ -49,7 +49,7 @@ export class KycService {
.single();
if (wallet.error) return wallet;

const query = admin.from('vault_entities').select('address').eq('type', 'kyc_provider');
const query = admin.from('vault_entities').select('address').eq('type', 'whitelist_provider');
if (wallet.data.discriminator) {
query.eq('tenant', dto.user_id);
} else {
Expand Down Expand Up @@ -78,7 +78,7 @@ export class KycService {
if (errors.length) return { error: new AggregateError(errors) };

const existing = await admin
.from('kyc_events')
.from('whitelist_events')
.select()
.eq('address', dto.address)
.eq('user_id', dto.user_id)
Expand All @@ -89,33 +89,36 @@ export class KycService {
if (existing.data) return { data: [existing.data] };

return admin
.from('kyc_events')
.from('whitelist_events')
.insert({ ...dto, event_name: 'accepted' })
.select();
}

private async checkOnChain(
kycProviders: Tables<'vault_entities'>[],
whiteListProviders: Tables<'vault_entities'>[],
address: string,
): Promise<ServiceResponse<boolean>> {
const errors = [];
let status = false;

for (const kyc of kycProviders) {
const provider = await this.getOnChainProvider(kyc.address);
for (const whiteListProvider of whiteListProviders) {
const provider = await this.getOnChainProvider(whiteListProvider.address);
const { error, data } = await responseFromRead(provider, provider.status(address));
if (error) {
errors.push(error);
continue;
}

status = status && data;
if (!status) break;
// If the address is white listed by ANY active WhiteListProvider, it is deemed white listed.
if (data) {
status = true;
break;
}
}
return errors.length > 0 ? { error: new AggregateError(errors) } : { data: status };
}

private async getOnChainProvider(address: string) {
return CredbullKYCProvider__factory.connect(address, await this.ethers.operator());
return CredbullWhiteListProvider__factory.connect(address, await this.ethers.operator());
}
}
22 changes: 10 additions & 12 deletions packages/api/src/modules/vaults/sync-vaults.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,13 @@ export class SyncVaultsService {
return {
type: 'fixed_yield_upside',
status: 'created' as const,
deposits_opened_at: toISOString(Number(params.fixedYieldVaultParams.windowVaultParams.depositWindow.opensAt)),
deposits_closed_at: toISOString(Number(params.fixedYieldVaultParams.windowVaultParams.depositWindow.closesAt)),
redemptions_opened_at: toISOString(Number(params.fixedYieldVaultParams.windowVaultParams.matureWindow.opensAt)),
redemptions_closed_at: toISOString(
Number(params.fixedYieldVaultParams.windowVaultParams.matureWindow.closesAt),
),
deposits_opened_at: toISOString(Number(params.fixedYieldVault.windowPlugin.depositWindow.opensAt)),
deposits_closed_at: toISOString(Number(params.fixedYieldVault.windowPlugin.depositWindow.closesAt)),
redemptions_opened_at: toISOString(Number(params.fixedYieldVault.windowPlugin.redemptionWindow.opensAt)),
redemptions_closed_at: toISOString(Number(params.fixedYieldVault.windowPlugin.redemptionWindow.closesAt)),
address: event.args.vault,
strategy_address: event.args.vault,
asset_address: params.fixedYieldVaultParams.maturityVaultParams.baseVaultParams.asset,
asset_address: params.fixedYieldVault.maturityVault.vault.asset,
tenant,
} as Tables<'vaults'>;
}
Expand All @@ -163,13 +161,13 @@ export class SyncVaultsService {
return {
type: 'fixed_yield',
status: 'created' as const,
deposits_opened_at: toISOString(Number(params.windowVaultParams.depositWindow.opensAt)),
deposits_closed_at: toISOString(Number(params.windowVaultParams.depositWindow.opensAt)),
redemptions_opened_at: toISOString(Number(params.windowVaultParams.matureWindow.opensAt)),
redemptions_closed_at: toISOString(Number(params.windowVaultParams.matureWindow.closesAt)),
deposits_opened_at: toISOString(Number(params.windowPlugin.depositWindow.opensAt)),
deposits_closed_at: toISOString(Number(params.windowPlugin.depositWindow.opensAt)),
redemptions_opened_at: toISOString(Number(params.windowPlugin.redemptionWindow.opensAt)),
redemptions_closed_at: toISOString(Number(params.windowPlugin.redemptionWindow.closesAt)),
address: event.args.vault,
strategy_address: event.args.vault,
asset_address: params.maturityVaultParams.baseVaultParams.asset,
asset_address: params.maturityVault.vault.asset,
tenant,
} as Tables<'vaults'>;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/modules/vaults/vaults.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class VaultsDto {

export class EntitiesDto {
@Expose()
type: 'treasury' | 'custodian' | 'activity_reward' | 'vault' | 'kyc_provider';
type: 'treasury' | 'custodian' | 'activity_reward' | 'vault' | 'whitelist_provider';

@Expose()
address: string;
Expand Down Expand Up @@ -77,7 +77,7 @@ export class VaultParamsDto extends EntitiesDto {

@ApiProperty({ type: String, example: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512' })
@IsString()
kycProvider: string;
whiteListProvider: string;

@ApiProperty({ type: String, example: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' })
@IsString()
Expand All @@ -97,7 +97,7 @@ export class VaultParamsDto extends EntitiesDto {

@ApiProperty({ type: String, example: '1000000000', description: 'Should be in wei with 6 decimals' })
@IsString()
depositThresholdForWhitelisting: string;
depositThresholdForWhiteListing: string;

@ApiProperty({
type: EntitiesDto,
Expand Down
Loading

0 comments on commit 13eec92

Please sign in to comment.