Skip to content

Commit 7f32793

Browse files
committed
[simplex]: add Aerodrome funding in fx strategy
1 parent b13204a commit 7f32793

File tree

13 files changed

+1001
-16
lines changed

13 files changed

+1001
-16
lines changed

sdk/packages/sdk/src/configs/ChainConfigService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ export class ChainConfigService {
103103
return (this.getConfig(chain)?.addresses.UniswapRouter02 ?? "0x") as HexString
104104
}
105105

106+
getAerodromeRouterAddress(chain: string): HexString {
107+
return (this.getConfig(chain)?.addresses.AerodromeRouter ?? "0x") as HexString
108+
}
109+
106110
getUniswapV2FactoryAddress(chain: string): HexString {
107111
return (this.getConfig(chain)?.addresses.UniswapV2Factory ?? "0x") as HexString
108112
}

sdk/packages/sdk/src/configs/chain.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ export interface ChainConfigData {
123123
Usdt0Oft?: `0x${string}`
124124
/** SolverAccount contract address used for EIP-7702 delegation */
125125
SolverAccount?: `0x${string}`
126+
/** Aerodrome (Solidly-style) router for LP removal / swaps on chains where Aerodrome is deployed */
127+
AerodromeRouter?: `0x${string}`
126128
}
127129
rpcEnvKey?: string
128130
defaultRpcUrl?: string
@@ -447,6 +449,7 @@ export const chainConfigs: Record<number, ChainConfigData> = {
447449
Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
448450
Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
449451
EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
452+
AerodromeRouter: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
450453
// Usdt0Oft: Not available on Base
451454
},
452455
rpcEnvKey: "BASE_MAINNET",

sdk/packages/simplex/src/bin/simplex.ts

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { parse } from "toml"
77
import { IntentFiller } from "@/core/filler"
88
import { BasicFiller } from "@/strategies/basic"
99
import { FXFiller } from "@/strategies/fx"
10+
import type { AerodromePoolConfig, OutputFundingConfig } from "@/funding/types"
1011
import { ConfirmationPolicy, FillerBpsPolicy, FillerPricePolicy } from "@/config/interpolated-curve"
11-
import { ChainConfig, FillerConfig, HexString } from "@hyperbridge/sdk"
12+
import { ChainConfig, Chains, FillerConfig, HexString, getConfigByStateMachineId } from "@hyperbridge/sdk"
1213
import {
1314
FillerConfigService,
1415
UserProvidedChainConfig,
@@ -69,6 +70,11 @@ interface BasicStrategyConfig {
6970
confirmationPolicies: Record<string, ChainConfirmationPolicy>
7071
}
7172

73+
/** TOML row for an Aerodrome pool; `chain` is the state machine id e.g. `EVM-8453`. */
74+
interface AerodromePoolToml extends AerodromePoolConfig {
75+
chain: string
76+
}
77+
7278
interface FxStrategyConfig {
7379
type: "hyperfx"
7480
/**
@@ -95,6 +101,12 @@ interface FxStrategyConfig {
95101
exoticTokenAddresses: Record<string, HexString>
96102
/** Optional per-chain confirmation policies for cross-chain orders */
97103
confirmationPolicies?: Record<string, ChainConfirmationPolicy>
104+
/** Optional Aerodrome LP funding for destination-chain outputs */
105+
outputFunding?: {
106+
aerodrome?: {
107+
pools?: AerodromePoolToml[]
108+
}
109+
}
98110
}
99111

100112
type StrategyConfig = BasicStrategyConfig | FxStrategyConfig
@@ -366,6 +378,7 @@ program
366378
const configuredSigner = initializeSignerFromToml(config.simplex.signer)
367379
const chainClientManager = new ChainClientManager(configService, configuredSigner)
368380
const runtimeSigner: SigningAccount = chainClientManager.getSigner()
381+
369382
const contractService = new ContractInteractionService(
370383
chainClientManager,
371384
configService,
@@ -408,6 +421,24 @@ program
408421
"No confirmationPolicies configured for hyperfx strategy; cross-chain orders will be skipped",
409422
)
410423
}
424+
let outputFunding: OutputFundingConfig | undefined
425+
if (strategyConfig.outputFunding?.aerodrome?.pools?.length) {
426+
const poolsByChain: Record<string, AerodromePoolConfig[]> = {}
427+
for (const row of strategyConfig.outputFunding.aerodrome.pools) {
428+
const chain = row.chain
429+
if (!poolsByChain[chain]) poolsByChain[chain] = []
430+
poolsByChain[chain].push({
431+
pair: row.pair,
432+
gauge: row.gauge,
433+
})
434+
}
435+
436+
outputFunding = {
437+
aerodrome: {
438+
poolsByChain,
439+
},
440+
}
441+
}
411442
return new FXFiller(
412443
runtimeSigner,
413444
configService,
@@ -418,13 +449,38 @@ program
418449
strategyConfig.maxOrderUsd,
419450
strategyConfig.exoticTokenAddresses,
420451
fxConfirmationPolicy,
452+
outputFunding,
421453
)
422454
}
423455
default:
424456
throw new Error(`Unknown strategy type: ${(strategyConfig as StrategyConfig).type}`)
425457
}
426458
})
427459

460+
// Initialise FXFiller strategies
461+
for (const strategy of strategies) {
462+
if (strategy instanceof FXFiller) {
463+
logger.info("Hydrating Aerodrome funding state...")
464+
await strategy.initialise()
465+
}
466+
}
467+
468+
// Set up periodic Aerodrome state refresh (~12s)
469+
const FUNDING_REFRESH_INTERVAL_MS = 12_000
470+
const fundingRefreshTimers: ReturnType<typeof setInterval>[] = []
471+
for (const strategy of strategies) {
472+
if (strategy instanceof FXFiller) {
473+
const timer = setInterval(async () => {
474+
try {
475+
await strategy.refreshFundingState()
476+
} catch (err) {
477+
logger.error({ err }, "Aerodrome funding state refresh failed")
478+
}
479+
}, FUNDING_REFRESH_INTERVAL_MS)
480+
fundingRefreshTimers.push(timer)
481+
}
482+
}
483+
428484
// Initialize rebalancing service only if fully configured
429485
let rebalancingService: RebalancingService | undefined
430486
const rebalancingConfig = configService.getRebalancingConfig()
@@ -516,19 +572,18 @@ program
516572
)
517573

518574
// Handle graceful shutdown
519-
process.on("SIGINT", async () => {
520-
logger.warn("Shutting down intent filler (SIGINT)...")
575+
const shutdown = async (signal: string) => {
576+
logger.warn(`Shutting down intent filler (${signal})...`)
577+
for (const timer of fundingRefreshTimers) {
578+
clearInterval(timer)
579+
}
521580
metrics?.stop()
522581
await intentFiller.stop()
523582
process.exit(0)
524-
})
583+
}
525584

526-
process.on("SIGTERM", async () => {
527-
logger.warn("Shutting down intent filler (SIGTERM)...")
528-
metrics?.stop()
529-
await intentFiller.stop()
530-
process.exit(0)
531-
})
585+
process.on("SIGINT", () => shutdown("SIGINT"))
586+
process.on("SIGTERM", () => shutdown("SIGTERM"))
532587
} catch (error) {
533588
// Use console.error for initial startup errors since logger might not be configured yet
534589
console.error("Failed to start filler:", error)
@@ -622,6 +677,29 @@ function validateConfig(config: FillerTomlConfig): void {
622677
}
623678

624679
if (strategy.type === "hyperfx") {
680+
if (strategy.outputFunding?.aerodrome) {
681+
if (strategy.outputFunding.aerodrome.pools?.length) {
682+
for (const pool of strategy.outputFunding.aerodrome.pools) {
683+
if (!pool.chain?.trim()) {
684+
throw new Error(
685+
"Each Aerodrome outputFunding pool must have a non-empty 'chain' (e.g. EVM-8453)",
686+
)
687+
}
688+
if (!pool.pair) {
689+
throw new Error("Each Aerodrome pool must include a 'pair' address")
690+
}
691+
const chainCfg = getConfigByStateMachineId(pool.chain as Chains)
692+
const aerodromeRouter = chainCfg?.addresses.AerodromeRouter
693+
const z = aerodromeRouter?.toLowerCase()
694+
if (!z || z === "0x" || z === "0x0000000000000000000000000000000000000000") {
695+
throw new Error(
696+
`Aerodrome pool ${pool.pair} uses chain ${pool.chain} but SDK chain config has no addresses.AerodromeRouter for that chain`,
697+
)
698+
}
699+
}
700+
}
701+
}
702+
625703
// Validate bid price curve
626704
if (
627705
!strategy.bidPriceCurve ||
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* Minimal Aerodrome / Solidly-style router, pair, and gauge ABIs.
3+
* Deployments may differ slightly; extend fragments if a chain adds functions.
4+
*/
5+
6+
export const AERODROME_PAIR_ABI = [
7+
{
8+
name: "token0",
9+
type: "function",
10+
stateMutability: "view",
11+
inputs: [],
12+
outputs: [{ type: "address" }],
13+
},
14+
{
15+
name: "token1",
16+
type: "function",
17+
stateMutability: "view",
18+
inputs: [],
19+
outputs: [{ type: "address" }],
20+
},
21+
{
22+
name: "stable",
23+
type: "function",
24+
stateMutability: "view",
25+
inputs: [],
26+
outputs: [{ type: "bool" }],
27+
},
28+
{
29+
name: "totalSupply",
30+
type: "function",
31+
stateMutability: "view",
32+
inputs: [],
33+
outputs: [{ type: "uint256" }],
34+
},
35+
{
36+
name: "getReserves",
37+
type: "function",
38+
stateMutability: "view",
39+
inputs: [],
40+
outputs: [
41+
{ name: "reserve0", type: "uint256" },
42+
{ name: "reserve1", type: "uint256" },
43+
{ name: "blockTimestampLast", type: "uint256" },
44+
],
45+
},
46+
{
47+
name: "balanceOf",
48+
type: "function",
49+
stateMutability: "view",
50+
inputs: [{ name: "account", type: "address" }],
51+
outputs: [{ type: "uint256" }],
52+
},
53+
{
54+
name: "approve",
55+
type: "function",
56+
stateMutability: "nonpayable",
57+
inputs: [
58+
{ name: "spender", type: "address" },
59+
{ name: "amount", type: "uint256" },
60+
],
61+
outputs: [{ type: "bool" }],
62+
},
63+
] as const
64+
65+
export const AERODROME_ROUTER_ABI = [
66+
{
67+
name: "quoteRemoveLiquidity",
68+
type: "function",
69+
stateMutability: "view",
70+
inputs: [
71+
{ name: "tokenA", type: "address" },
72+
{ name: "tokenB", type: "address" },
73+
{ name: "stable", type: "bool" },
74+
{ name: "liquidity", type: "uint256" },
75+
],
76+
outputs: [
77+
{ name: "amountA", type: "uint256" },
78+
{ name: "amountB", type: "uint256" },
79+
],
80+
},
81+
{
82+
name: "removeLiquidity",
83+
type: "function",
84+
stateMutability: "nonpayable",
85+
inputs: [
86+
{ name: "tokenA", type: "address" },
87+
{ name: "tokenB", type: "address" },
88+
{ name: "stable", type: "bool" },
89+
{ name: "liquidity", type: "uint256" },
90+
{ name: "amountAMin", type: "uint256" },
91+
{ name: "amountBMin", type: "uint256" },
92+
{ name: "to", type: "address" },
93+
{ name: "deadline", type: "uint256" },
94+
],
95+
outputs: [
96+
{ name: "amountA", type: "uint256" },
97+
{ name: "amountB", type: "uint256" },
98+
],
99+
},
100+
] as const
101+
102+
export const AERODROME_GAUGE_ABI = [
103+
{
104+
name: "withdraw",
105+
type: "function",
106+
stateMutability: "nonpayable",
107+
inputs: [{ name: "amount", type: "uint256" }],
108+
outputs: [],
109+
},
110+
{
111+
name: "balanceOf",
112+
type: "function",
113+
stateMutability: "view",
114+
inputs: [{ name: "account", type: "address" }],
115+
outputs: [{ type: "uint256" }],
116+
},
117+
{
118+
name: "stakingToken",
119+
type: "function",
120+
stateMutability: "view",
121+
inputs: [],
122+
outputs: [{ type: "address" }],
123+
},
124+
] as const

0 commit comments

Comments
 (0)