Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Liquidity Manager #66

Merged
merged 41 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1798819
liquidity manager file structure
carlosfebres Nov 21, 2024
dd91856
Merge branch 'main' into carlos/liquidity-manager
carlosfebres Nov 26, 2024
e77a5dc
rebalancing logic
carlosfebres Nov 27, 2024
c106ee7
solve lint warnings
carlosfebres Nov 27, 2024
1ad5216
increase liquidity manager processor concurrency
carlosfebres Nov 28, 2024
57e9923
liquidity provider structure
carlosfebres Dec 3, 2024
5c93992
LiFi implementation
carlosfebres Dec 6, 2024
6cef674
Merge branch 'main' into carlos/liquidity-manager
carlosfebres Dec 7, 2024
dc6a7fa
fix jest module name mapper
carlosfebres Dec 7, 2024
1ffe60a
increase check balances cron job interval
carlosfebres Dec 7, 2024
1cfe48c
fix unit tests
carlosfebres Dec 8, 2024
1c2307c
update eco routes package
carlosfebres Dec 17, 2024
61c47c2
update beta eco routes version
StoyanD Dec 17, 2024
cd0ccac
fix optional prover
StoyanD Dec 17, 2024
1350771
only accept patch updates automatically for eco routes
StoyanD Dec 17, 2024
f1201f1
upgrading to eco routes-ts ^0.1.10-beta
StoyanD Dec 19, 2024
6071ee9
change warning logs to debug logs
carlosfebres Dec 19, 2024
40294c4
fix table formatting
carlosfebres Dec 19, 2024
73aef2d
Merge branch 'ts-audit' into carlos/liquidity-manager
carlosfebres Dec 19, 2024
01285c2
move bigint conversion (#68)
StoyanD Dec 19, 2024
96d62ce
Batch fulfill (#71)
StoyanD Dec 20, 2024
9c7b1d9
Proof time change (#73)
StoyanD Dec 31, 2024
9a82eb3
@eco-foundation/routes beta
carlosfebres Jan 1, 2025
bbb0925
use job schedulers
carlosfebres Jan 1, 2025
bc8721d
move liquidity manager parameters to config
carlosfebres Jan 1, 2025
6022a7d
avoid returning undefined
carlosfebres Jan 1, 2025
3d355f7
rename LiquidityProviderService
carlosfebres Jan 2, 2025
0a6f442
move job interval to config
carlosfebres Jan 2, 2025
99186a7
refactor job manager class
carlosfebres Jan 2, 2025
0b453ec
reuse errors
carlosfebres Jan 2, 2025
7342858
jest test init
carlosfebres Jan 2, 2025
dc66b62
Disable storage prover (#74)
StoyanD Jan 7, 2025
70c423c
update @eco-foundation/routes-ts version
carlosfebres Jan 8, 2025
d401432
upgrade LiFi SDK version
carlosfebres Jan 8, 2025
175e872
LiFiProviderService service unit tests
carlosfebres Jan 8, 2025
dc6ef99
Same chain bug (#78)
StoyanD Jan 8, 2025
e966ce0
Add check to unsubscribe (#75)
StoyanD Jan 8, 2025
72e0906
Ed 4570 retry unfeasable (#76)
StoyanD Jan 8, 2025
8b104e7
Api (#77)
StoyanD Jan 8, 2025
4d8151e
Merge branch 'main' into carlos/liquidity-manager
StoyanD Jan 8, 2025
75d09fe
clean up
StoyanD Jan 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ module.exports = {
collectCoverageFrom: ['**/*.(t|j)s'],
coverageDirectory: '../coverage',
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>//$1',
},
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
},
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.592.0",
"@eco-foundation/routes": "^0.0.508-beta",
"@eco-foundation/routes": "~0.0.711-beta",
StoyanD marked this conversation as resolved.
Show resolved Hide resolved
"@launchdarkly/node-server-sdk": "^9.7.1",
"@liaoliaots/nestjs-redis-health": "^9.0.4",
"@lifi/sdk": "^3.4.1",
"@nestjs/bullmq": "^10.1.1",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.2",
Expand All @@ -48,6 +49,7 @@
"redlock": "^5.0.0-beta.2",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"table": "^6.8.2",
StoyanD marked this conversation as resolved.
Show resolved Hide resolved
"viem": "^2.21.32"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { HealthModule } from './health/health.module'
import { ProcessorModule } from './bullmq/processors/processor.module'
import { SolverModule } from './solver/solver.module'
import { FlagsModule } from './flags/flags.module'
import { LiquidityManagerModule } from '@/liquidity-manager/liquidity-manager.module'

@Module({
imports: [
Expand Down Expand Up @@ -41,6 +42,7 @@ import { FlagsModule } from './flags/flags.module'
}),
ProverModule,
SolverModule,
LiquidityManagerModule,
...getPino(),
],
controllers: [],
Expand Down
143 changes: 101 additions & 42 deletions src/balance/balance.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'
import { groupBy, zipWith } from 'lodash'
import { EcoConfigService } from '../eco-configs/eco-config.service'
import { Solver } from '../eco-configs/eco-config.types'
import { getDestinationNetworkAddressKey } from '../common/utils/strings'
import { EcoLogMessage } from '../common/logging/eco-log-message'
import { erc20Abi, Hex } from 'viem'
import { erc20Abi, Hex, MulticallParameters, MulticallReturnType } from 'viem'
import { ViemEventLog } from '../common/events/viem'
import { decodeTransferLog, isSupportedTokenType } from '../contracts'
import { KernelAccountClientService } from '../transaction/smart-wallets/kernel/kernel-account-client.service'

type TokenBalance = { decimals: bigint; balance: bigint }
import { TokenBalance, TokenConfig } from '@/balance/types'

/**
* Service class for getting configs for the app
Expand All @@ -25,13 +24,8 @@ export class BalanceService implements OnApplicationBootstrap {
) {}

async onApplicationBootstrap() {
//iterate over all solvers
await Promise.all(
Object.entries(this.ecoConfig.getSolvers()).map(async (entry) => {
const [, solver] = entry
await this.loadTokenBalances(solver)
}),
)
// iterate over all tokens
await Promise.all(this.getTokens().map((token) => this.loadTokenBalance(token)))
}

/**
Expand Down Expand Up @@ -69,20 +63,104 @@ export class BalanceService implements OnApplicationBootstrap {
}
}

getTokens(): TokenConfig[] {
return Object.values(this.ecoConfig.getSolvers()).flatMap((solver) => {
return Object.entries(solver.targets)
.filter(([, targetContract]) => isSupportedTokenType(targetContract.contractType))
.map(([tokenAddress, targetContract]) => ({
address: tokenAddress as Hex,
chainId: solver.chainID,
type: targetContract.contractType,
minBalance: targetContract.minBalance,
targetBalance: targetContract.targetBalance,
}))
})
}

async fetchTokenBalances(
chainID: number,
tokenAddresses: Hex[],
): Promise<Record<Hex, TokenBalance>> {
const client = await this.kernelAccountClientService.getClient(chainID)
const walletAddress = client.kernelAccount.address

this.logger.debug(
EcoLogMessage.fromDefault({
message: `fetchTokenBalances`,
properties: {
chainID,
tokenAddresses,
walletAddress,
},
}),
)

const results = (await client.multicall({
contracts: tokenAddresses.flatMap((tokenAddress): MulticallParameters['contracts'] => [
{
abi: erc20Abi,
address: tokenAddress,
functionName: 'balanceOf',
args: [walletAddress],
},
{
abi: erc20Abi,
address: tokenAddress,
functionName: 'decimals',
},
]),
allowFailure: false,
})) as MulticallReturnType

const result: Record<Hex, TokenBalance> = {}

tokenAddresses.forEach((tokenAddress, index) => {
const [balance = 0n, decimals = 0] = [results[index * 2], results[index * 2 + 1]]
result[tokenAddress] = {
address: tokenAddress,
balance: balance as bigint,
decimals: decimals as number,
}
})

return result
}

async fetchTokenBalance(chainID: number, tokenAddress: Hex): Promise<TokenBalance> {
const result = await this.fetchTokenBalances(chainID, [tokenAddress])
return result[tokenAddress]
}

async getAllTokenData() {
const tokens = this.getTokens()
const tokensByChainId = groupBy(tokens, 'chainId')
const chainIds = Object.keys(tokensByChainId)

const balancesPerChainIdPromise = chainIds.map(async (chainId) => {
const configs = tokensByChainId[chainId]
const tokenAddresses = configs.map((token) => token.address)
const balances = await this.fetchTokenBalances(parseInt(chainId), tokenAddresses)
return zipWith(configs, Object.values(balances), (config, balance) => ({
config,
balance,
chainId: parseInt(chainId),
}))
})

return Promise.all(balancesPerChainIdPromise).then((result) => result.flat())
}

/**
* Loads the token balance of the solver
* @returns
*/
private async loadTokenBalances(solver: Solver) {
await Promise.all(
Object.entries(solver.targets).map(async (target) => {
const [tokenAddress, targetContract] = target
if (isSupportedTokenType(targetContract.contractType)) {
//load the balance in the local mapping
return await this.loadERC20TokenBalance(solver.chainID, tokenAddress as Hex)
}
}),
)
private async loadTokenBalance(token: TokenConfig) {
switch (token.type) {
case 'erc20':
return this.loadERC20TokenBalance(token.chainId, token.address)
default:
throw new Error('Unsupported token type')
}
StoyanD marked this conversation as resolved.
Show resolved Hide resolved
}

private async loadERC20TokenBalance(
Expand All @@ -91,27 +169,8 @@ export class BalanceService implements OnApplicationBootstrap {
): Promise<TokenBalance | undefined> {
const key = getDestinationNetworkAddressKey(chainID, tokenAddress)
if (!this.tokenBalances.has(key)) {
const client = await this.kernelAccountClientService.getClient(chainID)
const erc20 = {
address: tokenAddress,
abi: erc20Abi,
}

const [{ result: balance }, { result: decimals }] = await client.multicall({
contracts: [
{
...erc20,
functionName: 'balanceOf',
args: [client.kernelAccount.address],
},
{
...erc20,
functionName: 'decimals',
},
],
})

this.tokenBalances.set(key, { balance: balance ?? 0n, decimals: BigInt(decimals ?? 0) })
const tokenBalance = await this.fetchTokenBalance(chainID, tokenAddress)
this.tokenBalances.set(key, tokenBalance)
}
return this.tokenBalances.get(key)
}
Expand Down
12 changes: 12 additions & 0 deletions src/balance/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import { Hex } from 'viem'
import { TargetContractType } from '@/eco-configs/eco-config.types'

export type TokenConfig = {
address: Hex
chainId: number
minBalance: number
targetBalance: number
type: TargetContractType
}

export type TokenBalance = {
address: string
decimals: number
balance: bigint
}
37 changes: 29 additions & 8 deletions src/bullmq/bullmq.helper.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,41 @@
import { BullModule } from '@nestjs/bullmq'
import { BullModule, RegisterQueueOptions } from '@nestjs/bullmq'
import { DynamicModule } from '@nestjs/common'
import { EcoConfigService } from '../eco-configs/eco-config.service'
import { RedisConnectionUtils } from '../common/redis/redis-connection-utils'
import { QueueInterface } from '../common/redis/constants'
import { EcoConfigService } from '@/eco-configs/eco-config.service'
import { RedisConnectionUtils } from '@/common/redis/redis-connection-utils'
import { QueueMetadata } from '@/common/redis/constants'

/**
* Initialize the BullMQ queue with the given token and eco configs
* @param token the name of the queue
* @param {QueueMetadata} queueInterface queue interface
* @param {Partial<RegisterQueueOptions>} opts queue options
* @returns
*/
export function initBullMQ(queueInterface: QueueInterface): DynamicModule {
export function initBullMQ(
queueInterface: QueueMetadata,
opts?: Partial<RegisterQueueOptions>,
): DynamicModule {
return BullModule.registerQueueAsync({
name: queueInterface.queue,
useFactory: async (configService: EcoConfigService) => {
return RedisConnectionUtils.getQueueOptions(queueInterface, configService.getRedis())
useFactory: (configService: EcoConfigService) => {
return {
...RedisConnectionUtils.getQueueOptions(queueInterface, configService.getRedis()),
...opts,
}
},
inject: [EcoConfigService],
})
}

/**
* Initialize the BullMQ flow with the given name and eco configs
* @param {QueueMetadata} queueInterface queue interface
* @returns
*/
export function initFlowBullMQ(queueInterface: QueueMetadata): DynamicModule {
return BullModule.registerFlowProducerAsync({
name: queueInterface.queue,
useFactory: (configService: EcoConfigService) =>
RedisConnectionUtils.getQueueOptions(queueInterface, configService.getRedis()),
inject: [EcoConfigService],
})
}
5 changes: 4 additions & 1 deletion src/common/redis/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ export const QUEUES: Record<any, QueueInterface> = {
},
}

export interface QueueInterface {
export interface QueueMetadata {
queue: string
prefix: string
}

export interface QueueInterface extends QueueMetadata {
jobs: Record<string, string>
}
4 changes: 2 additions & 2 deletions src/common/redis/redis-connection-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { RegisterQueueOptions } from '@nestjs/bullmq'
import * as Redis from 'ioredis'
import { EcoError } from '../errors/eco-error'
import { EcoLogMessage } from '../logging/eco-log-message'
import { QueueInterface } from './constants'
import { QueueMetadata } from './constants'
import { RedisConfig } from '../../eco-configs/eco-config.types'
import { RedlockRedisClient } from '../../nest-redlock/nest-redlock.service'

Expand All @@ -23,7 +23,7 @@ export class RedisConnectionUtils {
return new Redis.Redis(connection as Redis.RedisOptions)
}

static getQueueOptions(queue: QueueInterface, redisConfig: RedisConfig): RegisterQueueOptions {
static getQueueOptions(queue: QueueMetadata, redisConfig: RedisConfig): RegisterQueueOptions {
try {
const connection = redisConfig.connection
const { queue: name, prefix } = queue
Expand Down
24 changes: 20 additions & 4 deletions src/eco-configs/eco-config.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common'
import * as _ from 'lodash'
import * as config from 'config'
import { EcoLogMessage } from '../common/logging/eco-log-message'
import { EcoLogMessage } from '@/common/logging/eco-log-message'
import { ConfigSource } from './interfaces/config-source.interface'
import { EcoConfigType, Solver, IntentSource } from './eco-config.types'
import { EcoConfigType, IntentSource, Solver } from './eco-config.types'
import { entries } from 'lodash'
import { getAddress } from 'viem'
import { addressKeys } from '../common/viem/utils'
import { addressKeys, getRpcUrl } from '@/common/viem/utils'
import { ChainsSupported } from '@/common/chains/supported'
import { getChainConfig } from './utils'

/**
Expand Down Expand Up @@ -66,7 +68,10 @@
})
const config = getChainConfig(intent.chainID)
intent.sourceAddress = config.IntentSource
intent.provers = [config.Prover, config.HyperProver]
intent.provers = [config.HyperProver]
if(config.Prover) {

Check failure on line 72 in src/eco-configs/eco-config.service.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `·`
intent.provers.push(config.Prover)
}
return intent
})
return intents
Expand Down Expand Up @@ -128,4 +133,15 @@
getServer(): EcoConfigType['server'] {
return this.get('server')
}

getChainRPCs() {
const { apiKey, networks } = this.getAlchemy()
const supportedAlchemyChainIds = _.map(networks, 'id')

const entries = ChainsSupported.map((chain) => {
const rpcApiKey = supportedAlchemyChainIds.includes(chain.id) ? apiKey : undefined
return [chain.id, getRpcUrl(chain, rpcApiKey).url]
})
return Object.fromEntries(entries) as Record<number, string>
}
}
1 change: 1 addition & 0 deletions src/eco-configs/eco-config.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export interface TargetContract {
contractType: TargetContractType
selectors: string[]
minBalance: number
targetBalance: number
}

/**
Expand Down
Loading
Loading