Skip to content

Commit

Permalink
feat(backend): implement arbitrum mainnet camelot amm v3 resolver (#3)
Browse files Browse the repository at this point in the history
Adds CamelotV3 AMM resolver.
Updates controller to support multiple resolvers.

---------

Signed-off-by: Luca Georges Francois <[email protected]>
  • Loading branch information
0xpanoramix authored May 12, 2024
1 parent eb8b728 commit 473da24
Show file tree
Hide file tree
Showing 10 changed files with 3,013 additions and 34 deletions.
24 changes: 20 additions & 4 deletions backend/src/positions/dto/list-positions.dto.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import { IsNotEmpty } from 'class-validator';
import { Type } from 'class-transformer';
import {
IsArray,
IsEnum,
IsEthereumAddress,
IsNotEmpty,
IsNumber,
} from 'class-validator';
import { ProtocolName } from '../resolver-registry/implementations/constants';

/**
* Parameters for the `/positions` endpoint, used to specify:
*
* - The Ethereum Address of the owner of the positions.
* - The list of networks to fetch positions from.
* - The list of protocols to fetch positions from.
*/
export class ListPositionsDTO {
// TODO: Validate that the owner is a valid Ethereum Address.
@IsNotEmpty()
@IsEthereumAddress()
owner: string;

// TODO: Add a field to specify the list of chains.
// TODO: Add a field to specify the list of protocols.
@Type(() => Number)
@IsArray()
@IsNumber({}, { each: true })
chainIDs: number[];

@IsArray()
@IsEnum(ProtocolName, { each: true })
protocols: ProtocolName[];
}
37 changes: 29 additions & 8 deletions backend/src/positions/positions.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Controller, Get, Query } from '@nestjs/common';
import {
Controller,
Get,
Query,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { PositionsService } from './positions.service';
import { ListPositionsDTO } from './dto/list-positions.dto';
import { Position } from './entities/position.entity';
import { ResolverRegistryService } from './resolver-registry/resolver-registry.service';
import { mainnet } from 'viem/chains';
import { arbitrum, mainnet } from 'viem/chains';
import { createPublicClient, getAddress, http } from 'viem';
import { ResolverPositionExtra } from './resolver-registry/resolver.entity';
import { ListPositionsRO } from './ro/list-positions.ro';

@Controller('positions')
export class PositionsController {
Expand All @@ -23,18 +28,34 @@ export class PositionsController {
}),
});

// TODO: Get UniswapV3 mainnet deployment addresses from config.
// TODO: Get HTTP endpoint from config.
const arbitrumMainnetClient = createPublicClient({
chain: arbitrum,
transport: http(undefined, {
batch: {
batchSize: 10,
},
}),
});

// TODO: Get UniswapV3 ethereum mainnet deployment addresses from config.
this.resolverRegistryService.registerUniswapV3Resolver(
ethMainnetClient,
getAddress('0x1F98431c8aD98523631AE4a59f267346ea31F984'),
getAddress('0xC36442b4a4522E871399CD717aBDD847Ab11FE88'),
);

// TODO: Get CamelotV3 arbitrum mainnet deployment addresses from config.
this.resolverRegistryService.registerCamelotV3Resolver(
arbitrumMainnetClient,
getAddress('0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B'),
getAddress('0x00c7f3082833e796A5b3e4Bd59f6642FF44DCD15'),
);
}

@UsePipes(new ValidationPipe({ transform: true }))
@Get()
async findAll(
@Query() query: ListPositionsDTO,
): Promise<Position<ResolverPositionExtra>[]> {
async findAll(@Query() query: ListPositionsDTO): Promise<ListPositionsRO> {
return this.positionsService.findAll(query, this.resolverRegistryService);
}
}
55 changes: 38 additions & 17 deletions backend/src/positions/positions.service.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,54 @@
import { Injectable } from '@nestjs/common';
import { ListPositionsDTO } from './dto/list-positions.dto';
import { Position } from './entities/position.entity';
import { ResolverRegistryService } from './resolver-registry/resolver-registry.service';
import { getAddress } from 'viem';
import { ResolverPositionExtra } from './resolver-registry/resolver.entity';
import {
Resolver,
ResolverPositionExtra,
} from './resolver-registry/resolver.entity';
import { ListPositionsRO, PositionsPerProtocol } from './ro/list-positions.ro';

@Injectable()
export class PositionsService {
async findAll(
query: ListPositionsDTO,
resolverRegistryService: ResolverRegistryService,
): Promise<Position<ResolverPositionExtra>[]> {
): Promise<ListPositionsRO> {
const owner = getAddress(query.owner);
const store = resolverRegistryService.getStore();

const positions = (
await Promise.all(
Array.from(store.values()).map((resolver) => {
return Promise.all(
resolver.map((r) =>
r.findAllPositions<ResolverPositionExtra>(owner),
// Quick and dirty filtering of resolvers based on the query.
const store = new Map<number, Resolver[]>(
[...resolverRegistryService.getStore()]
.filter(
([chainID, resolvers]) =>
query.chainIDs.includes(chainID) &&
resolvers.some((resolver) =>
query.protocols.includes(resolver.getProtocolName()),
),
);
}),
)
)
.flat()
.flat();
)
.map(([chain, resolvers]) => [
chain,
resolvers.filter((resolver) =>
query.protocols.includes(resolver.getProtocolName()),
),
]),
);

return positions;
const res: ListPositionsRO = {};

// Note that we don't use Promise.all here because for some reason viem does not work properly in that case.
for (const [chainID, resolvers] of store) {
const positions: PositionsPerProtocol = {};

for (const resolver of resolvers) {
const resolverPositions =
await resolver.findAllPositions<ResolverPositionExtra>(owner);
positions[resolver.getProtocolName()] = resolverPositions;
}

res[chainID] = positions;
}

return res;
}
}
Loading

0 comments on commit 473da24

Please sign in to comment.