@@ -5,19 +5,9 @@ import (
55 "log/slog"
66 "os"
77 "os/signal"
8- "strconv"
98 "syscall"
109
11- "github.com/ethereum/go-ethereum/common"
12- "github.com/ethereum/go-ethereum/ethclient"
13- "github.com/joho/godotenv"
14- "github.com/robfig/cron/v3"
15-
1610 "remora/internal/agent"
17- liquidityrepo "remora/internal/liquidity/repository"
18- liquidityservice "remora/internal/liquidity/service"
19- "remora/internal/signer"
20- strategyservice "remora/internal/strategy/service"
2111)
2212
2313func main () {
@@ -26,195 +16,23 @@ func main() {
2616 }))
2717 slog .SetDefault (logger )
2818
29- // Load .env file
30- if err := godotenv .Load (); err != nil {
31- logger .Warn ("no .env file found, using environment variables" )
32- }
33-
34- // Initialize signer
35- sgn , err := signer .NewFromEnv ()
36- if err != nil {
37- logger .Error ("failed to create signer" , slog .Any ("error" , err ))
38- os .Exit (1 )
39- }
40-
41- logger .Info ("signer initialized" , slog .String ("address" , sgn .Address ().Hex ()))
42-
43- // Initialize eth client
44- rpcURL := os .Getenv ("RPC_URL" )
45- if rpcURL == "" {
46- logger .Error ("RPC_URL not set" )
47- os .Exit (1 )
48- }
49-
50- ethClient , err := ethclient .Dial (rpcURL )
51- if err != nil {
52- logger .Error ("failed to connect to RPC" , slog .Any ("error" , err ))
53- os .Exit (1 )
54- }
55-
56- defer ethClient .Close ()
57-
58- logger .Info ("connected to RPC" , slog .String ("url" , rpcURL ))
59-
60- // Initialize vault source from factory
61- factoryAddr := os .Getenv ("FACTORY_ADDRESS" )
62- if factoryAddr == "" {
63- logger .Error ("FACTORY_ADDRESS not set" )
64- os .Exit (1 )
65- }
66-
67- vaultSource := agent .NewFactoryVaultSource (
68- ethClient ,
69- common .HexToAddress (factoryAddr ),
70- )
71-
72- // Initialize liquidity repository + strategy service
73- stateViewAddr := os .Getenv ("STATEVIEW_CONTRACT_ADDR" )
74- if stateViewAddr == "" {
75- logger .Error ("STATEVIEW_CONTRACT_ADDR not set" )
76- os .Exit (1 )
77- }
19+ ctx , cancel := context .WithCancel (context .Background ())
20+ defer cancel ()
7821
79- liqRepo , err := liquidityrepo .New (liquidityrepo.Config {
80- RPCURL : rpcURL ,
81- ContractAddress : stateViewAddr ,
82- })
22+ rebalanceStop , err := agent .StartCron (ctx , logger , true )
8323 if err != nil {
84- logger .Error ("failed to init liquidity repository " , slog .Any ("error" , err ))
24+ logger .Error ("rebalance cron start failed " , slog .Any ("error" , err ))
8525 os .Exit (1 )
8626 }
87- defer liqRepo .Close ()
88-
89- liqSvc := liquidityservice .New (liqRepo )
90- strategySvc := strategyservice .New (liqSvc )
91-
92- // Initialize agent service
93- agentSvc := agent .New (
94- vaultSource ,
95- strategySvc ,
96- sgn ,
97- ethClient ,
98- logger ,
99- liqRepo ,
100- )
101-
102- // Load protection settings from env
103- swapSlippage := os .Getenv ("SWAP_SLIPPAGE_BPS" )
104- mintSlippage := os .Getenv ("MINT_SLIPPAGE_BPS" )
105- maxGasPrice := os .Getenv ("MAX_GAS_PRICE_GWEI" )
106- devThreshold := os .Getenv ("DEVIATION_THRESHOLD" )
107- tickRangeEnv := os .Getenv ("TICK_RANGE_AROUND_CURRENT" )
108-
109- sSlippage := int64 (50 ) // default 0.5%
110- if swapSlippage != "" {
111- if val , err := strconv .ParseInt (swapSlippage , 10 , 64 ); err == nil {
112- sSlippage = val
113- }
114- }
115-
116- mSlippage := int64 (50 ) // default 0.5%
117- if mintSlippage != "" {
118- if val , err := strconv .ParseInt (mintSlippage , 10 , 64 ); err == nil {
119- mSlippage = val
120- }
121- }
122-
123- mGasPrice := 1.0 // default 1.0 Gwei
124- if maxGasPrice != "" {
125- if val , err := strconv .ParseFloat (maxGasPrice , 64 ); err == nil {
126- mGasPrice = val
127- }
128- }
129-
130- dThreshold := 0.1 // default 10%
131- if devThreshold != "" {
132- if val , err := strconv .ParseFloat (devThreshold , 64 ); err == nil {
133- dThreshold = val
134- }
135- }
136-
137- if tickRangeEnv != "" {
138- if val , err := strconv .ParseInt (tickRangeEnv , 10 , 32 ); err == nil && val > 0 {
139- agentSvc .SetTickRangeAroundCurrent (int32 (val ))
140- logger .Info ("tick range override set" , slog .String ("raw" , tickRangeEnv ), slog .Int64 ("value" , val ))
141- } else {
142- logger .Warn ("invalid TICK_RANGE_AROUND_CURRENT" , slog .String ("raw" , tickRangeEnv ))
143- }
144- } else {
145- logger .Info ("no TICK_RANGE_AROUND_CURRENT provided" )
146- }
147-
148- agentSvc .SetProtectionSettings (sSlippage , mSlippage , mGasPrice )
149- agentSvc .SetDeviationThreshold (dThreshold )
150-
151- ctx , cancel := context .WithCancel (context .Background ())
152- exitCode := 0
153-
154- defer func () {
155- cancel ()
156-
157- if exitCode != 0 {
158- os .Exit (exitCode )
159- }
160- }()
161-
162- // Setup cron scheduler
163- schedule := parseRebalanceSchedule ()
164- c := cron .New ()
165-
166- _ , err = c .AddFunc (schedule , func () {
167- runAgent (ctx , agentSvc , logger )
168- })
169- if err != nil {
170- logger .Error ("invalid cron schedule" , slog .Any ("error" , err ))
17127
172- exitCode = 1
173-
174- return
28+ if rebalanceStop != nil {
29+ defer rebalanceStop ()
17530 }
17631
177- c .Start ()
178- logger .Info ("agent started" , slog .String ("schedule" , schedule ))
179-
180- // Run immediately on startup
181- runAgent (ctx , agentSvc , logger )
182-
183- // Handle shutdown
18432 interrupt := make (chan os.Signal , 1 )
18533 signal .Notify (interrupt , os .Interrupt , syscall .SIGHUP , syscall .SIGQUIT , syscall .SIGTERM )
18634
18735 <- interrupt
18836 logger .Info ("shutting down..." )
189- c .Stop ()
19037 cancel ()
19138}
192-
193- func runAgent (ctx context.Context , agentSvc * agent.Service , logger * slog.Logger ) {
194- logger .InfoContext (ctx , "running rebalance check" )
195-
196- results , err := agentSvc .Run (ctx )
197- if err != nil {
198- logger .ErrorContext (ctx , "rebalance run failed" , slog .Any ("error" , err ))
199- return
200- }
201-
202- for _ , r := range results {
203- logger .InfoContext (ctx , "vault processed" ,
204- slog .String ("address" , r .VaultAddress .Hex ()),
205- slog .Bool ("rebalanced" , r .Rebalanced ),
206- slog .String ("reason" , r .Reason ),
207- )
208- }
209-
210- logger .InfoContext (ctx , "rebalance check completed" , slog .Int ("vaults" , len (results )))
211- }
212-
213- func parseRebalanceSchedule () string {
214- schedule := os .Getenv ("REBALANCE_SCHEDULE" )
215- if schedule == "" {
216- return "*/5 * * * *" // default: every 5 minutes
217- }
218-
219- return schedule
220- }
0 commit comments