├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── cmd ├── root.go ├── run.go ├── scan.go ├── updateTokens.go ├── version.go ├── watch.go └── writeconfig.go ├── config.example.json ├── config └── config.go ├── contracts ├── bep20 │ └── IBEP20.go ├── build.sh ├── flash_loans │ ├── contracts │ │ ├── Arbitrage.sol │ │ ├── Migrations.sol │ │ ├── PancakeLibrary.sol │ │ └── interfaces │ │ │ ├── BakerySwap.sol │ │ │ ├── IERC20.sol │ │ │ ├── IPancakeRouter.sol │ │ │ ├── IUniswapV2Callee.sol │ │ │ ├── IUniswapV2Factory.sol │ │ │ ├── IUniswapV2Pair.sol │ │ │ ├── IUniswapV2Router01.sol │ │ │ ├── IUniswapV2Router02.sol │ │ │ └── IValueLiquidRouter.sol │ ├── migrations │ │ ├── 1_initial_migration.js │ │ └── 2_deploy_contracts.js │ ├── package.json │ ├── test │ │ ├── .gitkeep │ │ └── testArbitrage.js │ └── truffle-config.js ├── pancake_pair │ └── IPancakePair.go └── src │ └── IBEP20.sol ├── exchanges ├── apeswap │ └── apeswap.go ├── bakery │ └── mdex.go ├── main.go ├── mdex │ └── mdex.go ├── pancake │ ├── build.sh │ ├── contracts │ │ ├── build │ │ │ ├── IERC20.abi │ │ │ ├── IERC20.bin │ │ │ ├── IPancakeFactory.abi │ │ │ ├── IPancakeFactory.bin │ │ │ ├── IPancakePair.abi │ │ │ ├── IPancakePair.bin │ │ │ ├── IPancakeRouter01.abi │ │ │ ├── IPancakeRouter01.bin │ │ │ ├── IPancakeRouter02.abi │ │ │ ├── IPancakeRouter02.bin │ │ │ ├── IWETH.abi │ │ │ └── IWETH.bin │ │ ├── factory │ │ │ └── IPancakeFactory.go │ │ ├── pair │ │ │ └── IPancakePair.go │ │ ├── router │ │ │ └── IPancakeRouter01.go │ │ └── src │ │ │ └── PancakeRouter.sol │ ├── pancake.go │ └── pancake_test.go └── pandayield │ └── pandayield.go ├── go.mod ├── go.sum ├── main.go ├── services ├── bsc_client │ └── bsc_client.go ├── runners │ ├── arbitrage.go │ ├── arbitrage_test.go │ └── contracts │ │ ├── arbitrage │ │ └── IArbitrage.go │ │ ├── build.sh │ │ ├── build │ │ ├── IArbitrage.abi │ │ └── IArbitrage.bin │ │ └── src │ │ └── IArbitrage.sol ├── scanners │ └── lp_scanner.go └── wallet │ └── wallet.go ├── storage ├── db.go ├── models │ ├── base.go │ ├── liquidity_pool.go │ ├── token.go │ └── transaction.go └── price_graph │ ├── graph.go │ └── main.go ├── tokens ├── lp_tokens.go ├── supported_tokens.go └── token_info.go └── utils ├── db_types.go ├── errors.go ├── format.go └── system.go /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | defiarb 3 | /.idea/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Defi arbitrage bot (BSC) 2 | 3 | __Flash loan arbitrage bot for BSC__ 4 | 5 | _The bot is not profitable as-is_ 6 | 7 | ## Installation 8 | The project relies on a PostgreSQL database to keep trusted tokens and LPs lists 9 | 10 | ### From binary 11 | 1. Download the binary 12 | 2. Run `chmod +x defiarb` 13 | 3. Copy the file in a _$PATH_ directory (eg. `/usr/bin`) 14 | 4. [Customize configuration](#Config file) 15 | 16 | ### From source 17 | 1. Clone the repo 18 | 19 | `git clone ...` 20 | 21 | 2. Install dependencies 22 | 23 | `go get ./...` 24 | 25 | 3. [Customize configuration](#Config file) 26 | 27 | 28 | ### Config file 29 | 30 | Copy `config.example.json` and edit it according to your needs: 31 | - `NodeUrl`: bsc node URL 32 | - `ContractAddress`: bsc node URL 33 | - `Db`: PostgreSQL db config 34 | - `Host`: DB host (default localhost) 35 | - `Port`: DB port 36 | - `DbName`: DB name 37 | - `User`: Username 38 | - `Password`: Password 39 | 40 | ### Smart contract 41 | The arbitrage smart contract is in `contracts/flash_loans`, deploy it with `truffle migrate` and add the resulting address in `config.json`. 42 | 43 | ## Usage 44 | 45 | Before starting the arbitrage bot the database should be updated: 46 | 1. Update trusted tokens from trustwallet list with command `updateTokens` 47 | 2. Update LP tokens list for desired exchanges with command `scan [EXCHANGE_1 ... EXCHANGE_n]` (this command may take a long time to complete) 48 | 3. Run the arbitrage bot with `run` command 49 | 50 | ### Help 51 | Run `defiarb --help` for command list and `defiarb [COMMAND] --help` for detailed command help 52 | 53 | ### Write example config file 54 | `defiarb writeconfig [CONFIG_FILE.json]` 55 | 56 | ### Update token list 57 | `defiarb updateTokens` 58 | 59 | ### Update LP tokens 60 | `defiarb scan [EXCHANGE_1 ... EXCHANGE_n]` 61 | 62 | 63 | ### Run arbitrage bot 64 | `defiarb run` 65 | 66 | 67 | ## Contributing 68 | 69 | The project was meant as a learning project, so it is not regularly maintained but any contributions you make are **greatly appreciated**. 70 | 71 | 1. Fork the Project 72 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 73 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 74 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 75 | 5. Open a Pull Request -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export CGO_ENABLED=0 3 | export VERSION=$(eval "git describe --tags --abbrev=0") 4 | export BRANCH=$(eval "git branch | grep \* | cut -d ' ' -f2") 5 | export REV=$(eval "git rev-parse HEAD") 6 | go build -o defiarb -ldflags "-X main.Branch=`echo $BRANCH` -X main.Revision=`echo $REV` -X main.Version=`echo "$VERSION"` " main.go 7 | echo "$VERSION ($BRANCH - $REV)" 8 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "fmt" 20 | "github.com/niccoloCastelli/defiarb/config" 21 | "github.com/spf13/cobra" 22 | "os" 23 | ) 24 | 25 | var ( 26 | cfgFile string 27 | walletAddr string 28 | conf *config.Config 29 | Version = "0.1.0" 30 | Revision = "" 31 | Branch = "" 32 | ) 33 | 34 | func SetVersion(version string, revision string, branch string) { 35 | Version = version 36 | Revision = revision 37 | Branch = branch 38 | rootCmd.Version = fmt.Sprintf("%s (rev. %s) (branch: %s)", Version, Revision, Branch) 39 | } 40 | 41 | // rootCmd represents the base command when called without any subcommands 42 | var rootCmd = &cobra.Command{ 43 | Use: "bscbot", 44 | Short: "Binance smart chain bot", 45 | Long: ``, 46 | // Uncomment the following line if your bare application 47 | // has an action associated with it: 48 | // Run: func(cmd *cobra.Command, args []string) { }, 49 | } 50 | 51 | // Execute adds all child commands to the root command and sets flags appropriately. 52 | // This is called by main.main(). It only needs to happen once to the rootCmd. 53 | func Execute() { 54 | if err := rootCmd.Execute(); err != nil { 55 | fmt.Println(err) 56 | os.Exit(1) 57 | } 58 | } 59 | 60 | func init() { 61 | cobra.OnInitialize(initConfig) 62 | 63 | // Here you will define your flags and configuration settings. 64 | // Cobra supports persistent flags, which, if defined here, 65 | // will be global for your application. 66 | 67 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bscbot.yaml)") 68 | rootCmd.PersistentFlags().StringVarP(&walletAddr, "address", "a", "", "Wallet address") 69 | } 70 | 71 | // initConfig reads in config file and ENV variables if set. 72 | func initConfig() { 73 | var err error 74 | if cfgFile != "" { 75 | // Use config file from the flag. 76 | conf, err = config.ReadConfig(cfgFile) 77 | } else { 78 | conf, err = config.ReadConfig("config.json") 79 | } 80 | if err != nil { 81 | return 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /cmd/run.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/ethclient" 8 | "github.com/niccoloCastelli/defiarb/contracts/bep20" 9 | "github.com/niccoloCastelli/defiarb/exchanges" 10 | _ "github.com/niccoloCastelli/defiarb/exchanges/apeswap" 11 | _ "github.com/niccoloCastelli/defiarb/exchanges/bakery" 12 | "github.com/niccoloCastelli/defiarb/exchanges/pancake" 13 | "github.com/niccoloCastelli/defiarb/exchanges/pancake/contracts/pair" 14 | "github.com/niccoloCastelli/defiarb/services/bsc_client" 15 | "github.com/niccoloCastelli/defiarb/services/runners" 16 | "github.com/niccoloCastelli/defiarb/storage" 17 | "github.com/niccoloCastelli/defiarb/storage/models" 18 | "github.com/niccoloCastelli/defiarb/storage/price_graph" 19 | "github.com/niccoloCastelli/defiarb/utils" 20 | "github.com/pkg/errors" 21 | "go.uber.org/atomic" 22 | "math/big" 23 | "os" 24 | "os/signal" 25 | "strings" 26 | "sync" 27 | "syscall" 28 | "time" 29 | 30 | "github.com/spf13/cobra" 31 | ) 32 | 33 | // runCmd represents the scanTokens command 34 | var runCmd = &cobra.Command{ 35 | Use: "run", 36 | Short: "Run arbitrage bot", 37 | Long: ``, 38 | RunE: func(cmd *cobra.Command, args []string) error { 39 | fmt.Println("scanTokens called") 40 | if conf == nil { 41 | return errors.New("config not found") 42 | } 43 | fmt.Println("scan called", walletAddr) 44 | ctx, cancelFn := context.WithCancel(context.Background()) 45 | defer cancelFn() 46 | stopChan := make(chan os.Signal) 47 | signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL) 48 | client, err := bsc_client.NewClient(conf.NodeUrl, ctx) 49 | if err != nil { 50 | return errors.WithStack(err) 51 | } 52 | 53 | Db, err := storage.NewDb(&conf.Db) 54 | if err != nil { 55 | return errors.WithStack(err) 56 | } 57 | Db = Db.LogMode(false) 58 | if err := storage.Migrate(Db); err != nil { 59 | return errors.WithStack(err) 60 | } 61 | exchangesMap := map[string]exchanges.Router{} 62 | for _, exchangeName := range exchanges.Registered() { 63 | exchange, err := exchanges.New(exchangeName, client, "") 64 | if err != nil { 65 | return err 66 | } 67 | exchangesMap[exchangeName] = exchange 68 | } 69 | 70 | supportedTokens := models.Tokens{} 71 | if err := supportedTokens.GetAll(Db); err != nil { 72 | return err 73 | } 74 | supportedTokensAddrs := make([]string, len(supportedTokens)) 75 | //startTokens := map[string]models.Token{} 76 | tokensMap := map[string]models.Token{} 77 | for i, tokenInfo := range supportedTokens { 78 | supportedTokensAddrs[i] = strings.ToLower(tokenInfo.Address) 79 | //token, err := bep20.NewBep20(common.HexToAddress(tokenInfo.Address), client) 80 | tokensMap[strings.ToLower(tokenInfo.Address)] = supportedTokens[i] 81 | } 82 | suppordedLps := models.LiquidityPools{} 83 | if err := suppordedLps.LoadSupportedPools(Db.LogMode(true)); err != nil { 84 | return err 85 | } 86 | priceChangesChan := make(chan models.LiquidityPool, len(suppordedLps)) 87 | errChan := make(chan error) 88 | lock := sync.Mutex{} 89 | wg := sync.WaitGroup{} 90 | validLps := []models.LiquidityPool{} 91 | fmt.Println("Scanning supported LPs") 92 | scannedLps := atomic.Int32{} 93 | for _, lp := range suppordedLps { 94 | exchange, ok := exchangesMap[lp.Exchange] 95 | if !ok { //|| exchange.Name() == "value" 96 | continue 97 | } 98 | if err := lp.GetTokens(Db); err != nil { 99 | fmt.Println("Db error: ", err.Error()) 100 | continue 101 | } 102 | wg.Add(1) 103 | go func(exchange exchanges.Router, lp models.LiquidityPool) { 104 | done := false 105 | defer func() { 106 | if !done { 107 | wg.Done() 108 | val := scannedLps.Inc() 109 | if val%100 == 0 { 110 | fmt.Printf("%d/%d\n", val, len(suppordedLps)) 111 | } 112 | } 113 | }() 114 | swapsChan := make(chan *pair.PairSwap, len(suppordedLps)) 115 | reserve0, reserve1, err := exchange.GetReserves(common.HexToAddress(lp.Address)) 116 | if err != nil { 117 | fmt.Println("ERROR (GetReserves): ", lp.Exchange, lp.Description, err.Error()) 118 | return 119 | } 120 | if lp.Token0.LoanAmount == 0 { 121 | token, err := bep20.NewBep20(common.HexToAddress(lp.Token0Address), client) 122 | if err != nil { 123 | return 124 | } 125 | decimals, err := token.Decimals(nil) 126 | if err != nil { 127 | return 128 | } 129 | lp.Token0.LoanAmount = int64(10 ^ int(decimals)) 130 | } 131 | if lp.Token1.LoanAmount == 0 { 132 | token, err := bep20.NewBep20(common.HexToAddress(lp.Token1Address), client) 133 | if err != nil { 134 | return 135 | } 136 | decimals, err := token.Decimals(nil) 137 | if err != nil { 138 | return 139 | } 140 | lp.Token1.LoanAmount = int64(10 ^ int(decimals)) 141 | } 142 | price, err := exchange.GetPairPrice(common.HexToAddress(lp.Address), lp.Token0.LoanAmount, lp.Token1.LoanAmount) 143 | if err != nil { 144 | fmt.Println("ERROR (GetPairPrice): ", lp.Exchange, lp.Description, err.Error()) 145 | return 146 | } 147 | if reserve0 == 0 || reserve1 == 0 { //|| price.Slippage() > 0.4 148 | fmt.Println("Skip pair ", lp.Exchange, lp.Description, reserve0, reserve1) 149 | return 150 | } 151 | lpPair, err := pair.NewPair(common.HexToAddress(lp.Address), client) 152 | if err != nil { 153 | fmt.Println("ERROR (NewPair): ", lp.Exchange, lp.Description, err.Error()) 154 | return 155 | } 156 | subscr, err := lpPair.WatchSwap(nil, swapsChan, nil, nil) 157 | if err != nil { 158 | fmt.Println("ERROR (WatchSwap): ", lp.Exchange, lp.Description, err.Error()) 159 | return 160 | } 161 | lp.Token0Price = price.Token0Price 162 | lp.Token1Price = price.Token1Price 163 | fmt.Println("Subscribed swaps for ", lp.Name) 164 | 165 | lock.Lock() 166 | validLps = append(validLps, lp) 167 | lock.Unlock() 168 | wg.Done() 169 | done = true 170 | 171 | fmt.Printf("supported lp:[%s] [%s] %s reserves: %f %f\n", lp.Exchange, lp.Description, price.String(), reserve0, reserve1) 172 | defer subscr.Unsubscribe() 173 | for { 174 | select { 175 | case _ = <-swapsChan: 176 | priceChangesChan <- lp 177 | case err := <-subscr.Err(): 178 | if err != nil { 179 | if err.Error() == "unexpected EOF" { 180 | continue 181 | } 182 | errChan <- err 183 | } 184 | case <-ctx.Done(): 185 | return 186 | } 187 | } 188 | }(exchange, lp) 189 | 190 | } 191 | wg.Wait() 192 | 193 | writeClient, err := ethclient.Dial(conf.NodeUrl) // PROD 194 | if err != nil { 195 | return err 196 | } 197 | arbChan := make(chan runners.ArbitrageRoute, 0) 198 | arbRunner := runners.NewArbitrageRunner(writeClient, common.HexToAddress(conf.ContractAddress)) //PROD 199 | if err = arbRunner.Run(ctx, arbChan); err != nil { 200 | return err 201 | } 202 | writeClientChainId, err := writeClient.ChainID(ctx) 203 | if err != nil { 204 | return err 205 | } 206 | fmt.Println("ArbitrageRunner started", writeClientChainId.String()) 207 | fmt.Printf("Watching %d pairs\n", len(validLps)) 208 | 209 | priceGraph := price_graph.NewPriceGraph(validLps...) 210 | for _, token := range supportedTokens { 211 | priceGraph.ShortestPath(token.Address) 212 | } 213 | fmt.Println("Start listening...") 214 | for { 215 | select { 216 | case lp := <-priceChangesChan: 217 | exchange, ok := exchangesMap[lp.Exchange] 218 | if !ok { 219 | continue 220 | } 221 | go func(exchange exchanges.Router, lp models.LiquidityPool) { 222 | tradeInfo, err := exchange.GetPairPrice(common.HexToAddress(lp.Address), lp.Token0.LoanAmount, lp.Token1.LoanAmount) 223 | if err != nil { 224 | fmt.Println("ERROR (priceChangesChan)", err.Error()) 225 | return 226 | } 227 | priceGraph.UpdatePrice(lp.Address, tradeInfo.Token0Price, tradeInfo.Token1Price) 228 | paths0, err := priceGraph.ShortestPath(lp.Token0Address) 229 | if err != nil { 230 | fmt.Println("ERROR (ShortestPath)", err.Error()) 231 | return 232 | } 233 | paths1, err := priceGraph.ShortestPath(lp.Token1Address) 234 | if err != nil { 235 | fmt.Println("ERROR (ShortestPath)", err.Error()) 236 | return 237 | } 238 | paths := append(paths0, paths1...) 239 | if len(paths) > 0 { 240 | for _, path := range paths { 241 | // fmt.Println("Opportunity") 242 | if (path.StartExchange == pancake.Name) && !arbRunner.IsBusy() && len(path.Tokens) > 1 { 243 | token1, ok := tokensMap[strings.ToLower(path.Tokens[1])] 244 | if !ok { 245 | continue 246 | } 247 | //m, _ := json.MarshalIndent(path, "", " ") 248 | 249 | router1 := exchangesMap[path.Exchanges[1]].GetRouterAddress() 250 | router2 := common.Address{} 251 | token2 := common.Address{} 252 | if len(path.Exchanges) > 2 { 253 | router2 = exchangesMap[path.Exchanges[2]].GetRouterAddress() 254 | token2 = common.HexToAddress(path.Tokens[2]) 255 | } 256 | 257 | amount1 := utils.GetAmount(token1.LoanAmount, int64(token1.Decimals)) 258 | fmt.Println("Flash opportunity!", len(paths), path.Name, path.Weight, time.Now(), amount1.String(), path.Exchanges, path.Tokens) //, string(m) 259 | fmt.Println(router1, router2, token2) 260 | if conf.ExecuteTrades { 261 | arbChan <- runners.ArbitrageRoute{ 262 | Key: path.Key, 263 | Router1: router1, 264 | Router2: router2, 265 | Token0: common.HexToAddress(path.Tokens[0]), 266 | Token1: common.HexToAddress(path.Tokens[1]), 267 | Token2: token2, 268 | Amount0: big.NewInt(int64(0)), 269 | Amount1: amount1, 270 | Amount2: big.NewInt(int64(0)), 271 | } 272 | } 273 | } 274 | } 275 | } 276 | }(exchange, lp) 277 | // fmt.Printf("price updated: [%s] [%s] %s\n", lp.Exchange, lp.Description, tradeInfo.String()) 278 | case err := <-errChan: 279 | return err 280 | case <-stopChan: 281 | fmt.Println("---- STOP ----") 282 | return nil 283 | } 284 | } 285 | }, 286 | } 287 | 288 | func init() { 289 | rootCmd.AddCommand(runCmd) 290 | } 291 | 292 | /* 293 | BUSD: https://bscscan.com/address/0xe9e7cea3dedca5984780bafc599bd69add087d56 294 | USDT: https://bscscan.com/address/0x55d398326f99059ff775485246999027b3197955 295 | BTC: https://bscscan.com/token/0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c 296 | ETH: https://bscscan.com/token/0x2170ed0880ac9a755fd29b2688956bd959f933f8 297 | DAI: https://bscscan.com/token/0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3 298 | DOT: https://bscscan.com/token/0x7083609fce4d1d8dc0c979aab8c869ea2c873402 299 | XRP: https://bscscan.com/token/0x1d2f0da169ceb9fc7b3144628db156f3f6c60dbe 300 | LINK: https://bscscan.com/token/0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd 301 | BAND: https://bscscan.com/token/0xad6caeb32cd2c308980a548bd0bc5aa4306c6c18 302 | LTC: https://bscscan.com/token/0x4338665cbb7b2485a8855a139b75d5e34ab0db94 303 | EOS: https://bscscan.com/token/0x56b6fb708fc5732dec1afc8d8556423a2edccbd6 304 | BCH: https://bscscan.com/token/0x8ff795a6f4d97e7887c79bea79aba5cc76444adf 305 | XTZ: https://bscscan.com/token/0x16939ef78684453bfdfb47825f8a5f714f12623a 306 | ONT: https://bscscan.com/token/0xfd7b3a77848f1c2d67e05e54d78d174a0c850335 307 | ADA: https://bscscan.com/token/0x3ee2200efb3400fabb9aacf31297cbdd1d435d47 308 | ATOM: https://bscscan.com/token/0x0eb3a705fc54725037cc9e008bdede697f62f335 309 | YFII: https://bscscan.com/token/0x7f70642d88cf1c4a3a7abb072b53b929b653eda5 310 | ZEC: https://bscscan.com/token/0x1ba42e5193dfa8b03d15dd1b86a3113bbbef8eeb 311 | */ 312 | -------------------------------------------------------------------------------- /cmd/scan.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/niccoloCastelli/defiarb/exchanges" 7 | _ "github.com/niccoloCastelli/defiarb/exchanges/bakery" 8 | _ "github.com/niccoloCastelli/defiarb/exchanges/pancake" 9 | "github.com/niccoloCastelli/defiarb/services/bsc_client" 10 | "github.com/niccoloCastelli/defiarb/storage" 11 | "github.com/pkg/errors" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var scanCmd = &cobra.Command{ 16 | Use: "scan", 17 | Short: "scan exchange1 exchange2", 18 | Long: `Find LP for exchange(s)`, 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | if conf == nil { 21 | return errors.New("config not found") 22 | } 23 | fmt.Println("scan called", walletAddr) 24 | ctx, cancelFn := context.WithCancel(context.Background()) 25 | defer cancelFn() 26 | client, err := bsc_client.NewClient(conf.NodeUrl, ctx) 27 | if err != nil { 28 | return errors.WithStack(err) 29 | } 30 | 31 | Db, err := storage.NewDb(&conf.Db) 32 | if err != nil { 33 | return errors.WithStack(err) 34 | } 35 | Db = Db.LogMode(true) 36 | if err := storage.Migrate(Db); err != nil { 37 | return errors.WithStack(err) 38 | } 39 | for _, exchangeName := range args { 40 | fmt.Println("Scan exchange: ", exchangeName) 41 | exchange, err := exchanges.New(exchangeName, client, "") 42 | if err != nil { 43 | return err 44 | } 45 | _, addrChan, errChan := exchange.GetAllPairsAsync(ctx) 46 | if len(errChan) > 0 { 47 | return <-errChan 48 | } 49 | for { 50 | select { 51 | case err := <-errChan: 52 | fmt.Println("Get pairs error: ", err.Error()) 53 | cancelFn() 54 | goto endloop 55 | case pairAddr := <-addrChan: 56 | if pairAddr == nil { 57 | goto endloop 58 | } 59 | lp, err := exchange.GetPairInfo(*pairAddr) 60 | if err != nil { 61 | fmt.Println(err) 62 | continue 63 | } 64 | if err := Db.Save(lp).Error; err != nil { 65 | return err 66 | } 67 | } 68 | } 69 | endloop: 70 | } 71 | return nil 72 | }, 73 | } 74 | 75 | /**/ 76 | 77 | func init() { 78 | rootCmd.AddCommand(scanCmd) 79 | 80 | // Here you will define your flags and configuration settings. 81 | 82 | // Cobra supports Persistent Flags which will work for this command 83 | // and all subcommands, e.g.: 84 | // scanCmd.PersistentFlags().String("foo", "", "A help for foo") 85 | 86 | // Cobra supports local flags which will only run when this command 87 | // is called directly, e.g.: 88 | // scanCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 89 | } 90 | -------------------------------------------------------------------------------- /cmd/updateTokens.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/ethclient" 8 | "github.com/niccoloCastelli/defiarb/storage" 9 | "github.com/niccoloCastelli/defiarb/storage/models" 10 | "github.com/pkg/errors" 11 | "github.com/spf13/cobra" 12 | "gopkg.in/src-d/go-git.v4" 13 | "gopkg.in/src-d/go-git.v4/plumbing/object" 14 | "gopkg.in/src-d/go-git.v4/storage/memory" 15 | "regexp" 16 | ) 17 | 18 | type tokenInfo struct { 19 | Name_ string `json:"name"` 20 | Website string `json:"website"` 21 | Description string `json:"description"` 22 | Explorer string `json:"explorer"` 23 | Type string `json:"type"` 24 | Symbol string `json:"symbol"` 25 | Decimals int `json:"decimals"` 26 | Status string `json:"status"` 27 | Id string `json:"id"` 28 | } 29 | 30 | func (t *tokenInfo) LogoUrl() string { 31 | return "" 32 | } 33 | 34 | func (t *tokenInfo) Code() string { 35 | return t.Symbol 36 | } 37 | 38 | func (t *tokenInfo) Name() string { 39 | return t.Name_ 40 | } 41 | 42 | func (t *tokenInfo) Address() string { 43 | return t.Id 44 | } 45 | 46 | func (t *tokenInfo) BalanceOf(client *ethclient.Client, account common.Address) (uint64, error) { 47 | return 0, errors.New("not supported") 48 | } 49 | 50 | func (t *tokenInfo) FormattedBalance(client *ethclient.Client, account common.Address) (float64, error) { 51 | return 0, errors.New("not supported") 52 | } 53 | 54 | // updateTokensCmd represents the updateTokens command 55 | var updateTokensCmd = &cobra.Command{ 56 | Use: "updateTokens", 57 | Short: "Update tokens list", 58 | Long: `Download token list from trustwallet github`, 59 | RunE: func(cmd *cobra.Command, args []string) error { 60 | 61 | Db, err := storage.NewDb(&conf.Db) 62 | if err != nil { 63 | return errors.WithStack(err) 64 | } 65 | 66 | matchAssetRe, err := regexp.Compile(`^blockchains/smartchain/assets/([0-9A-Za-z]+)/info\.json$`) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | fmt.Println("Cloning git repository...") 72 | r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{ 73 | URL: "https://github.com/trustwallet/assets.git", 74 | }) 75 | if err != nil { 76 | return err 77 | } 78 | head, err := r.Head() 79 | if err != nil { 80 | return err 81 | } 82 | commit, err := r.CommitObject(head.Hash()) 83 | if err != nil { 84 | return err 85 | } 86 | fileIter, err := commit.Files() 87 | if err != nil { 88 | return err 89 | } 90 | fmt.Println("Searching files...") 91 | err = fileIter.ForEach(func(f *object.File) error { 92 | 93 | m := matchAssetRe.FindStringSubmatch(f.Name) 94 | if len(m) == 0 { 95 | return nil 96 | } 97 | content, err := f.Contents() 98 | if err != nil { 99 | return err 100 | } 101 | 102 | tokenInfo := tokenInfo{} 103 | if err := json.Unmarshal([]byte(content), &tokenInfo); err != nil { 104 | return err 105 | } 106 | if tokenInfo.Type == "BEP20" && tokenInfo.Status == "active" { 107 | dbModel := models.NewTokenFromERC20(&tokenInfo) 108 | fmt.Println(tokenInfo.Name, tokenInfo.Symbol) 109 | err := Db.Save(&dbModel).Error 110 | if err != nil { 111 | return err 112 | } 113 | } 114 | return nil 115 | }) 116 | return err 117 | }, 118 | } 119 | 120 | func init() { 121 | rootCmd.AddCommand(updateTokensCmd) 122 | } 123 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // versionCmd represents the version command 10 | var versionCmd = &cobra.Command{ 11 | Use: "version", 12 | Short: "Print software version", 13 | Long: `Print software version`, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | fmt.Println(rootCmd.Version) 16 | }, 17 | } 18 | 19 | func init() { 20 | rootCmd.AddCommand(versionCmd) 21 | 22 | // Here you will define your flags and configuration settings. 23 | 24 | // Cobra supports Persistent Flags which will work for this command 25 | // and all subcommands, e.g.: 26 | // versionCmd.PersistentFlags().String("foo", "", "A help for foo") 27 | 28 | // Cobra supports local flags which will only run when this command 29 | // is called directly, e.g.: 30 | // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 31 | } 32 | -------------------------------------------------------------------------------- /cmd/watch.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/ethclient" 8 | "github.com/niccoloCastelli/defiarb/contracts/bep20" 9 | "github.com/niccoloCastelli/defiarb/services/wallet" 10 | "github.com/niccoloCastelli/defiarb/storage" 11 | "github.com/niccoloCastelli/defiarb/storage/models" 12 | "github.com/niccoloCastelli/defiarb/tokens" 13 | "github.com/pkg/errors" 14 | "github.com/spf13/cobra" 15 | "math" 16 | ) 17 | 18 | var walletCmd = &cobra.Command{ 19 | Use: "wallet", 20 | Short: "Check wallet status", 21 | Long: `Check wallet status`, 22 | RunE: func(cmd *cobra.Command, args []string) error { 23 | fmt.Println("wallet called", walletAddr) 24 | ctx := context.Background() 25 | Db, err := storage.NewDb(&conf.Db) 26 | if err != nil { 27 | return errors.WithStack(err) 28 | } 29 | Db = Db.LogMode(true) 30 | supportedLpTokens := models.LiquidityPools{} 31 | if err := supportedLpTokens.LoadSupportedPools(Db); err != nil { 32 | return err 33 | } 34 | client, err := ethclient.Dial(conf.NodeUrl) 35 | if err != nil { 36 | return err 37 | } 38 | chainId, err := client.ChainID(ctx) 39 | if err != nil { 40 | return err 41 | } 42 | fmt.Println("Chain ID: ", chainId.String()) 43 | address := common.HexToAddress(walletAddr) 44 | Wallet := wallet.NewWallet(address, client) 45 | balance, err := Wallet.GetBalance(ctx) 46 | if err != nil { 47 | return err 48 | } 49 | fmt.Println("Balance: ", float64(balance)/math.Pow(10, 18)) 50 | supportedTokens := tokens.SupportedTokens() 51 | for _, token := range supportedTokens { 52 | tokenBalance, err := token.FormattedBalance(client, address) 53 | if err != nil { 54 | return err 55 | } 56 | fmt.Printf("Balance: %s %f [%s]\n", token.Code(), tokenBalance, token.Address()) 57 | } 58 | 59 | for _, lpToken := range supportedLpTokens { 60 | tokenBalance, err := lpToken.LpToken().FormattedBalance(client, address) 61 | if err != nil { 62 | return err 63 | } 64 | fmt.Printf("Balance: %s %f [%s]\n", lpToken.Symbol, tokenBalance, lpToken.Address) 65 | } 66 | 67 | txs, err := Wallet.GetTransactions(ctx) 68 | if err != nil { 69 | return err 70 | } 71 | for _, tx := range txs { 72 | instance, err := bep20.NewBep20(tx.Address, client) 73 | if err != nil { 74 | return err 75 | } 76 | t, err := instance.ParseTransfer(tx) 77 | if err != nil { 78 | return err 79 | } 80 | fmt.Println(tx.Address.String(), t.From.String(), t.To.String(), float64(t.Value.Uint64())/math.Pow(10, 18)) 81 | } 82 | return nil 83 | }, 84 | } 85 | 86 | func init() { 87 | rootCmd.AddCommand(walletCmd) 88 | } 89 | -------------------------------------------------------------------------------- /cmd/writeconfig.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/niccoloCastelli/defiarb/config" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var writeconfigCmd = &cobra.Command{ 9 | Use: "writeconfig", 10 | Short: "Write example config", 11 | Long: `Write example config file. writeconfig [FILE_PATH (default ./config.json)]`, 12 | RunE: func(cmd *cobra.Command, args []string) error { 13 | if len(args) == 0 { 14 | args = []string{config.GetDefaultConfigLocation()} 15 | } 16 | for _, arg := range args { 17 | if err := config.WriteConfig(arg); err != nil { 18 | return err 19 | } 20 | } 21 | return nil 22 | }, 23 | } 24 | 25 | func init() { 26 | rootCmd.AddCommand(writeconfigCmd) 27 | 28 | // Here you will define your flags and configuration settings. 29 | 30 | // Cobra supports Persistent Flags which will work for this command 31 | // and all subcommands, e.g.: 32 | // writeconfigCmd.PersistentFlags().String("foo", "", "A help for foo") 33 | 34 | // Cobra supports local flags which will only run when this command 35 | // is called directly, e.g.: 36 | // writeconfigCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 37 | } 38 | -------------------------------------------------------------------------------- /config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "NodeUrl": "", 3 | "ContractAddress": "", 4 | "ExecuteTrades": false, 5 | "Db": { 6 | "Host": "localhost", 7 | "Port": "5432", 8 | "DbName": "bscbot", 9 | "User": "bscbot", 10 | "Password": "bscbot", 11 | "SslMode": false 12 | } 13 | } -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | type Config struct { 11 | NodeUrl string 12 | ContractAddress string 13 | ExecuteTrades bool 14 | Db DbConfig 15 | } 16 | 17 | type DbConfig struct { 18 | Host string 19 | Port string 20 | DbName string 21 | User string 22 | Password string 23 | SslMode bool 24 | } 25 | 26 | func (d DbConfig) ConnectionString() string { 27 | var sslmode string 28 | if d.SslMode { 29 | sslmode = "require" 30 | } else { 31 | sslmode = "disable" 32 | } 33 | return fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s", d.Host, d.Port, d.User, d.DbName, 34 | d.Password, sslmode) 35 | } 36 | func GetDefaultConfig() Config { 37 | return Config{ 38 | NodeUrl: "https://bsc-dataseed.binance.org/", 39 | ContractAddress: "", 40 | Db: DbConfig{ 41 | Host: "localhost", 42 | Port: "5432", 43 | DbName: "bscbot", 44 | User: "bscbot", 45 | Password: "bscbot", 46 | SslMode: false, 47 | }, 48 | } 49 | } 50 | 51 | func ReadConfig(fileName string) (*Config, error) { 52 | cfg := Config{} 53 | if fileName == "" { 54 | fileName = GetDefaultConfigLocation() 55 | } 56 | File, err := os.OpenFile(fileName, os.O_RDONLY, 0775) 57 | if err != nil { 58 | return nil, err 59 | } 60 | defer func() { _ = File.Close() }() 61 | FileContent, err := ioutil.ReadAll(File) 62 | if err != nil { 63 | return nil, err 64 | } 65 | err = json.Unmarshal(FileContent, &cfg) 66 | if err != nil { 67 | return nil, err 68 | } 69 | return &cfg, nil 70 | } 71 | 72 | func GetDefaultConfigLocation() string { 73 | return "config.json" 74 | } 75 | 76 | func WriteConfig(fileName string) error { 77 | defaultConfig := GetDefaultConfig() 78 | if fileName == "" { 79 | fileName = GetDefaultConfigLocation() 80 | } 81 | if f, err := ioutil.ReadFile(fileName); err == nil { 82 | if err := json.Unmarshal(f, &defaultConfig); err != nil { 83 | return err 84 | } 85 | } 86 | 87 | File, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0775) 88 | if err != nil { 89 | return err 90 | } 91 | defer func() { _ = File.Close() }() 92 | 93 | content, err := json.MarshalIndent(&defaultConfig, "", "\t") 94 | _, err = File.Write(content) 95 | return err 96 | } 97 | -------------------------------------------------------------------------------- /contracts/build.sh: -------------------------------------------------------------------------------- 1 | 2 | mkdir -p bep20 3 | solc --overwrite --abi --bin ./src/IBEP20.sol -o build 4 | abigen --bin=./build/IBEP20.bin --abi=./build/IBEP20.abi --pkg=bep20 --out=bep20/IBEP20.go 5 | 6 | rm -rf ./build 7 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/Arbitrage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import './PancakeLibrary.sol'; 5 | 6 | import './interfaces/IERC20.sol'; 7 | import './interfaces/IPancakeRouter.sol'; 8 | import './interfaces/IValueLiquidRouter.sol'; 9 | import './interfaces/BakerySwap.sol'; 10 | 11 | 12 | interface IArbitrage { 13 | function startArbitrage( 14 | address router1, 15 | address router2, 16 | address token0, 17 | address token1, 18 | address token2, 19 | uint amount0, 20 | uint amount1, 21 | uint amount2 22 | ) external payable; 23 | } 24 | 25 | // token0 -> token1 (token0 == 0) 26 | // token1 -> token2 (router1) 27 | // token2 -> token0 (router2) 28 | 29 | 30 | // token1 -> token0 (token1 == 0) 31 | // token0 -> token2 (router1) 32 | // token2 -> token1 (router2) 33 | 34 | // Router1, Router2, token0, token1, token2 35 | contract Arbitrage is IArbitrage { 36 | uint constant deadline = 1000000 days; 37 | 38 | IPancakeRouter02 public pancakeRouter; 39 | IPancakeRouter02 public bakeryRouter; 40 | IPancakeRouter02 public apeRouter; 41 | IValueLiquidRouter public valueRouter; 42 | 43 | address pancakeFactory; 44 | 45 | struct RouteArgs { 46 | address router1; 47 | address router2; 48 | address token2; 49 | uint amount2; 50 | } 51 | 52 | struct ArbInfo { 53 | address[] path; 54 | address[] path1; 55 | address[] path2; 56 | 57 | address token0; 58 | address token1; 59 | address token2; 60 | 61 | uint amount2; 62 | 63 | address router1; 64 | address router2; 65 | } 66 | 67 | event Called(address indexed from, bytes _data); 68 | 69 | constructor(address _pancakeRouter, address _bakeryRouter, address _valueRouter, address _apeRouter, address _pancakeFactory) public payable { 70 | pancakeRouter = IPancakeRouter02(_pancakeRouter); 71 | bakeryRouter = IPancakeRouter02(_bakeryRouter); 72 | valueRouter = IValueLiquidRouter(_valueRouter); 73 | apeRouter = IPancakeRouter02(_apeRouter); 74 | //Arbitrage.deployed().then((instance) => {return instance.startArbitrage("0xC0788A3aD43d79aa53B09c2EaCc313A787d1d607","0xC0788A3aD43d79aa53B09c2EaCc313A787d1d607","0xC0788A3aD43d79aa53B09c2EaCc313A787d1d607","0x603c7f932ed1fc6575303d8fb018fdcbb0f39a95","0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c","0x947950BcC74888a40Ffa2593C5798F11Fc9124C4","0","5000000000000000000","997376987535316000000")}) 75 | 76 | pancakeFactory = _pancakeFactory; 77 | } 78 | 79 | receive() external payable {} 80 | 81 | //TODO: only owner 82 | function startArbitrage( 83 | address router1, 84 | address router2, 85 | address token0, 86 | address token1, 87 | address token2, 88 | uint amount0, 89 | uint amount1, 90 | uint amount2 91 | ) override external payable { 92 | 93 | address pairAddress = IPancakeFactory(pancakeFactory).getPair(token0, token1); 94 | require(pairAddress != address(0), 'This pool does not exist'); 95 | 96 | IPancakePair(pairAddress).swap( 97 | IPancakePair(pairAddress).token0() == token0 ? 0 : amount1, 98 | IPancakePair(pairAddress).token0() == token0 ? amount1 : 0, 99 | address(this), 100 | abi.encode(RouteArgs(router1, router2, token2, amount2)) 101 | ); 102 | } 103 | 104 | function swapTokens(address router, address[] memory path, uint amountToken) private returns (uint) { 105 | uint amountReceived = 0; 106 | if (router == address(pancakeRouter) || router == address(apeRouter)) { 107 | 108 | address factoryAddr = IPancakeRouter02(router).factory(); 109 | address pairAddress = IPancakeFactory(factoryAddr).getPair(path[0], path[1]); 110 | require(pairAddress != address(0), 'This pool does not exist'); 111 | 112 | address[] memory sortedPath = new address[](2); 113 | sortedPath[0] = IPancakePair(pairAddress).token0(); 114 | sortedPath[1] = IPancakePair(pairAddress).token1(); 115 | 116 | if (sortedPath[0] == path[0]) { 117 | uint amountOut = IPancakeRouter02(router).getAmountsOut(amountToken, sortedPath)[1]; 118 | //IERC20(sortedPath[0]).approve(pairAddress, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); 119 | IERC20(sortedPath[0]).transfer(pairAddress, amountToken); 120 | IPancakePair(pairAddress).swap(0, amountOut, address(this), ""); 121 | amountReceived = amountOut; 122 | 123 | } else { 124 | address[] memory swapPath = new address[](2); 125 | swapPath[0] = sortedPath[1]; 126 | swapPath[1] = sortedPath[0]; 127 | 128 | uint amountOut = IPancakeRouter02(router).getAmountsOut(amountToken, swapPath)[1]; 129 | //IERC20(sortedPath[1]).approve(pairAddress, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); 130 | IERC20(sortedPath[1]).transfer(pairAddress, amountToken); 131 | IPancakePair(pairAddress).swap(amountOut, 0, address(this), ""); 132 | amountReceived = amountOut; 133 | } 134 | } else if (router == address(bakeryRouter)) { 135 | address factoryAddr = IPancakeRouter02(router).factory(); 136 | address pairAddress = IPancakeFactory(factoryAddr).getPair(path[0], path[1]); 137 | require(pairAddress != address(0), 'This pool does not exist'); 138 | 139 | address[] memory sortedPath = new address[](2); 140 | sortedPath[0] = IBakerySwapPair(pairAddress).token0(); 141 | sortedPath[1] = IBakerySwapPair(pairAddress).token1(); 142 | 143 | (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IBakerySwapPair(pairAddress).getReserves(); 144 | 145 | 146 | if (sortedPath[0] == path[0]) { 147 | uint amountOut = IPancakeRouter02(router).getAmountOut(amountToken, reserve0, reserve1); 148 | IERC20(sortedPath[0]).approve(pairAddress, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); 149 | IERC20(sortedPath[0]).transfer(pairAddress, amountToken); 150 | IBakerySwapPair(pairAddress).swap(0, amountOut, address(this)); 151 | amountReceived = amountOut; 152 | 153 | } else { 154 | address[] memory swapPath = new address[](2); 155 | swapPath[0] = sortedPath[1]; 156 | swapPath[1] = sortedPath[0]; 157 | 158 | uint amountOut = IPancakeRouter02(router).getAmountOut(amountToken, reserve1, reserve0); 159 | IERC20(sortedPath[1]).approve(pairAddress, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); 160 | IERC20(sortedPath[1]).transfer(pairAddress, amountToken); 161 | IBakerySwapPair(pairAddress).swap(amountOut, 0, address(this)); 162 | amountReceived = amountOut; 163 | } 164 | 165 | } else if (router == address(valueRouter)) { 166 | address pairAddress = IValueLiquidFactory(valueRouter.factory()).getPair(path[0], path[1], 50, 3); 167 | require(pairAddress != address(0), 'Value: pair not found'); 168 | address formula = valueRouter.formula(); 169 | 170 | address[] memory sortedPath = new address[](2); 171 | sortedPath[0] = IValueLiquidPair(pairAddress).token0(); 172 | sortedPath[1] = IValueLiquidPair(pairAddress).token1(); 173 | 174 | (uint112 reserve0,uint112 reserve1, uint32 _) = IValueLiquidPair(pairAddress).getReserves(); 175 | 176 | /* 177 | uint256 amountIn, 178 | uint256 reserveIn, 179 | uint256 reserveOut, 180 | uint32 tokenWeightIn, 181 | uint32 tokenWeightOut, 182 | uint32 swapFee 183 | */ 184 | 185 | 186 | if (sortedPath[0] == path[0]) { 187 | uint amountOut = IValueLiquidFormula(formula).getPairAmountOut(pairAddress, sortedPath[0], amountToken); 188 | IERC20(sortedPath[0]).approve(pairAddress, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); 189 | IERC20(sortedPath[0]).transfer(pairAddress, amountToken); 190 | IValueLiquidPair(pairAddress).swap(0, amountOut, address(this), ""); 191 | amountReceived = amountOut; 192 | } else { 193 | uint amountOut = IValueLiquidFormula(formula).getPairAmountOut(pairAddress, sortedPath[1], amountToken); 194 | IERC20(sortedPath[1]).approve(pairAddress, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); 195 | IERC20(sortedPath[1]).transfer(pairAddress, amountToken); 196 | IValueLiquidPair(pairAddress).swap(amountOut, 0, address(this), ""); 197 | amountReceived = amountOut; 198 | } 199 | } else { 200 | revert('Unknown router'); 201 | } 202 | require(amountReceived > 0, 'Amount received 0'); 203 | return amountReceived; 204 | } 205 | 206 | function pancakeCall(address _sender, uint _amount0, uint _amount1, bytes calldata _data) external { 207 | 208 | ArbInfo memory arbInfo = ArbInfo({ 209 | path : new address[](2), 210 | path1 : new address[](2), 211 | path2 : new address[](2), 212 | token0 : IPancakePair(msg.sender).token0(), 213 | token1 : IPancakePair(msg.sender).token1(), 214 | token2 : address(0), 215 | amount2 : 0, 216 | router1 : address(0), 217 | router2 : address(0) 218 | }); 219 | 220 | (arbInfo.router1, arbInfo.router2, arbInfo.token2, arbInfo.amount2) = abi.decode(_data, (address, address, address, uint)); 221 | 222 | require( 223 | msg.sender == PancakeLibrary.pairFor(pancakeFactory, arbInfo.token0, arbInfo.token1), 224 | 'Unauthorized' 225 | ); 226 | require(_amount0 == 0 || _amount1 == 0, 'Amount zero'); 227 | 228 | uint amountToken = _amount0 == 0 ? _amount1 : _amount0; 229 | uint amountReceived = 0; 230 | uint amountReceivedFinal = 0; 231 | 232 | arbInfo.path[0] = _amount0 == 0 ? arbInfo.token1 : arbInfo.token0; 233 | arbInfo.path[1] = _amount0 == 0 ? arbInfo.token0 : arbInfo.token1; 234 | 235 | 236 | IERC20 otherToken; 237 | 238 | if (_amount0 == 0) { 239 | _amount1 = IERC20(arbInfo.token1).balanceOf(address(this)); 240 | // token1 -> token2 -> token0 241 | // paths: [fl 0->1] [1-> 2] [2 -> 0] 242 | if (arbInfo.token2 == address(0)) { 243 | // Swap 1 244 | arbInfo.path1[0] = arbInfo.token1; 245 | arbInfo.path1[1] = arbInfo.token0; 246 | // Selezione router e scambio (amount1 -> amount2) 247 | amountReceivedFinal = swapTokens(arbInfo.router1, arbInfo.path1, _amount1); 248 | } else { 249 | // Swap 1 250 | arbInfo.path1[0] = arbInfo.token1; 251 | arbInfo.path1[1] = arbInfo.token2; 252 | // Selezione router e scambio (amount1 -> amount2) 253 | amountReceived = swapTokens(arbInfo.router1, arbInfo.path1, _amount1); 254 | 255 | // Swap 2 256 | arbInfo.path2[0] = arbInfo.token2; 257 | arbInfo.path2[1] = arbInfo.token0; 258 | 259 | //uint _amount2 = IERC20(arbInfo.token2).balanceOf(address(this)); 260 | // Selezione router e scambio (out_swap_1 -> amountRequired) 261 | amountReceivedFinal = swapTokens(arbInfo.router2, arbInfo.path2, amountReceived); 262 | } 263 | 264 | 265 | otherToken = IERC20(arbInfo.token0); 266 | arbInfo.path[0] = arbInfo.token0; 267 | arbInfo.path[1] = arbInfo.token1; 268 | } else { 269 | // token0 -> token2 -> token1 270 | // paths: [fl 1->0] [0-> 2] [2 -> 1] 271 | _amount0 = IERC20(arbInfo.token0).balanceOf(address(this)); 272 | 273 | if (arbInfo.token2 == address(0)) { 274 | // Swap 1 275 | arbInfo.path1[0] = arbInfo.token0; 276 | arbInfo.path1[1] = arbInfo.token1; 277 | 278 | // Selezione router e scambio (amount1 -> amount2) 279 | amountReceivedFinal = swapTokens(arbInfo.router1, arbInfo.path1, _amount0); 280 | } else { 281 | // Swap 1 282 | arbInfo.path1[0] = arbInfo.token0; 283 | arbInfo.path1[1] = arbInfo.token2; 284 | 285 | // Selezione router e scambio (amount1 -> amount2) 286 | amountReceived = swapTokens(arbInfo.router1, arbInfo.path1, _amount0); 287 | 288 | // Swap 2 289 | arbInfo.path2[0] = arbInfo.token2; 290 | arbInfo.path2[1] = arbInfo.token1; 291 | 292 | //uint _amount2 = IERC20(arbInfo.token2).balanceOf(address(this)); 293 | // Selezione router e scambio (out_swap_1 -> amountRequired) 294 | amountReceivedFinal = swapTokens(arbInfo.router2, arbInfo.path2, amountReceived); 295 | } 296 | otherToken = IERC20(arbInfo.token1); 297 | arbInfo.path[0] = arbInfo.token1; 298 | arbInfo.path[1] = arbInfo.token0; 299 | } 300 | 301 | // Importo da rimborsare 302 | uint amountRequired = PancakeLibrary.getAmountsIn( 303 | pancakeFactory, 304 | amountToken, 305 | arbInfo.path 306 | )[0]; 307 | 308 | require(amountReceivedFinal > amountRequired, 'amount required exceeds amount received'); 309 | 310 | 311 | // Restituzione flash loan 312 | otherToken.transfer(msg.sender, amountRequired); 313 | 314 | 315 | // Trasferimento profitto 316 | uint earning = otherToken.balanceOf(address(this)); 317 | otherToken.transfer(tx.origin, earning); 318 | } 319 | 320 | } 321 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.9.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/PancakeLibrary.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | import './interfaces/IPancakeRouter.sol'; 4 | 5 | import "@openzeppelin/contracts/math/SafeMath.sol"; 6 | 7 | library PancakeLibrary { 8 | using SafeMath for uint; 9 | 10 | // returns sorted token addresses, used to handle return values from pairs sorted in this order 11 | function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { 12 | require(tokenA != tokenB, 'PancakeLibrary: IDENTICAL_ADDRESSES'); 13 | (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 14 | require(token0 != address(0), 'PancakeLibrary: ZERO_ADDRESS'); 15 | } 16 | 17 | // calculates the CREATE2 address for a pair without making any external calls 18 | function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) { 19 | (address token0, address token1) = sortTokens(tokenA, tokenB); 20 | pair = address(uint(keccak256(abi.encodePacked( 21 | hex'ff', 22 | factory, 23 | keccak256(abi.encodePacked(token0, token1)), 24 | hex'd0d4c4cd0848c93cb4fd1f498d7013ee6bfb25783ea21593d5834f5d250ece66' // init code hash 25 | )))); 26 | } 27 | 28 | // fetches and sorts the reserves for a pair 29 | function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) { 30 | (address token0,) = sortTokens(tokenA, tokenB); 31 | pairFor(factory, tokenA, tokenB); 32 | (uint reserve0, uint reserve1,) = IPancakePair(pairFor(factory, tokenA, tokenB)).getReserves(); 33 | (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); 34 | } 35 | 36 | // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset 37 | function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) { 38 | require(amountA > 0, 'PancakeLibrary: INSUFFICIENT_AMOUNT'); 39 | require(reserveA > 0 && reserveB > 0, 'PancakeLibrary: INSUFFICIENT_LIQUIDITY'); 40 | amountB = amountA.mul(reserveB) / reserveA; 41 | } 42 | 43 | // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset 44 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) { 45 | require(amountIn > 0, 'PancakeLibrary: INSUFFICIENT_INPUT_AMOUNT'); 46 | require(reserveIn > 0 && reserveOut > 0, 'PancakeLibrary: INSUFFICIENT_LIQUIDITY'); 47 | uint amountInWithFee = amountIn.mul(998); 48 | uint numerator = amountInWithFee.mul(reserveOut); 49 | uint denominator = reserveIn.mul(1000).add(amountInWithFee); 50 | amountOut = numerator / denominator; 51 | } 52 | 53 | // given an output amount of an asset and pair reserves, returns a required input amount of the other asset 54 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) { 55 | require(amountOut > 0, 'PancakeLibrary: INSUFFICIENT_OUTPUT_AMOUNT'); 56 | require(reserveIn > 0 && reserveOut > 0, 'PancakeLibrary: INSUFFICIENT_LIQUIDITY'); 57 | uint numerator = reserveIn.mul(amountOut).mul(1000); 58 | uint denominator = reserveOut.sub(amountOut).mul(998); 59 | amountIn = (numerator / denominator).add(1); 60 | } 61 | 62 | // performs chained getAmountOut calculations on any number of pairs 63 | function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) { 64 | require(path.length >= 2, 'PancakeLibrary: INVALID_PATH'); 65 | amounts = new uint[](path.length); 66 | amounts[0] = amountIn; 67 | for (uint i; i < path.length - 1; i++) { 68 | (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]); 69 | amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut); 70 | } 71 | } 72 | 73 | // performs chained getAmountIn calculations on any number of pairs 74 | function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) { 75 | require(path.length >= 2, 'PancakeLibrary: INVALID_PATH'); 76 | amounts = new uint[](path.length); 77 | amounts[amounts.length - 1] = amountOut; 78 | for (uint i = path.length - 1; i > 0; i--) { 79 | (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]); 80 | amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/interfaces/BakerySwap.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | interface IBakerySwapPair { 4 | event Approval(address indexed owner, address indexed spender, uint256 value); 5 | event Transfer(address indexed from, address indexed to, uint256 value); 6 | 7 | function name() external pure returns (string memory); 8 | 9 | function symbol() external pure returns (string memory); 10 | 11 | function decimals() external pure returns (uint8); 12 | 13 | function totalSupply() external view returns (uint256); 14 | 15 | function balanceOf(address owner) external view returns (uint256); 16 | 17 | function allowance(address owner, address spender) external view returns (uint256); 18 | 19 | function approve(address spender, uint256 value) external returns (bool); 20 | 21 | function transfer(address to, uint256 value) external returns (bool); 22 | 23 | function transferFrom( 24 | address from, 25 | address to, 26 | uint256 value 27 | ) external returns (bool); 28 | 29 | function DOMAIN_SEPARATOR() external view returns (bytes32); 30 | 31 | function PERMIT_TYPEHASH() external pure returns (bytes32); 32 | 33 | function nonces(address owner) external view returns (uint256); 34 | 35 | function permit( 36 | address owner, 37 | address spender, 38 | uint256 value, 39 | uint256 deadline, 40 | uint8 v, 41 | bytes32 r, 42 | bytes32 s 43 | ) external; 44 | 45 | event Mint(address indexed sender, uint256 amount0, uint256 amount1); 46 | event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); 47 | event Swap( 48 | address indexed sender, 49 | uint256 amount0In, 50 | uint256 amount1In, 51 | uint256 amount0Out, 52 | uint256 amount1Out, 53 | address indexed to 54 | ); 55 | event Sync(uint112 reserve0, uint112 reserve1); 56 | 57 | function MINIMUM_LIQUIDITY() external pure returns (uint256); 58 | 59 | function factory() external view returns (address); 60 | 61 | function token0() external view returns (address); 62 | 63 | function token1() external view returns (address); 64 | 65 | function getReserves() 66 | external 67 | view 68 | returns ( 69 | uint112 reserve0, 70 | uint112 reserve1, 71 | uint32 blockTimestampLast 72 | ); 73 | 74 | function price0CumulativeLast() external view returns (uint256); 75 | 76 | function price1CumulativeLast() external view returns (uint256); 77 | 78 | function kLast() external view returns (uint256); 79 | 80 | function mint(address to) external returns (uint256 liquidity); 81 | 82 | function burn(address to) external returns (uint256 amount0, uint256 amount1); 83 | 84 | function swap( 85 | uint256 amount0Out, 86 | uint256 amount1Out, 87 | address to 88 | ) external; 89 | 90 | function skim(address to) external; 91 | 92 | function sync() external; 93 | 94 | function initialize(address, address) external; 95 | } 96 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IERC20 { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external view returns (string memory); 8 | function symbol() external view returns (string memory); 9 | function decimals() external view returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | } 18 | 19 | interface IWETH { 20 | function deposit() external payable; 21 | 22 | function transfer(address to, uint value) external returns (bool); 23 | 24 | function withdraw(uint) external; 25 | } -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/interfaces/IPancakeRouter.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at BscScan.com on 2020-09-19 3 | */ 4 | 5 | pragma solidity ^0.6.6; 6 | 7 | import './IERC20.sol'; 8 | 9 | 10 | interface IPancakeFactory { 11 | event PairCreated(address indexed token0, address indexed token1, address pair, uint); 12 | 13 | function feeTo() external view returns (address); 14 | 15 | function feeToSetter() external view returns (address); 16 | 17 | function getPair(address tokenA, address tokenB) external view returns (address pair); 18 | 19 | function allPairs(uint) external view returns (address pair); 20 | 21 | function allPairsLength() external view returns (uint); 22 | 23 | function createPair(address tokenA, address tokenB) external returns (address pair); 24 | 25 | function setFeeTo(address) external; 26 | 27 | function setFeeToSetter(address) external; 28 | } 29 | 30 | interface IPancakeRouter01 { 31 | function factory() external pure returns (address); 32 | 33 | function WETH() external pure returns (address); 34 | 35 | function addLiquidity( 36 | address tokenA, 37 | address tokenB, 38 | uint amountADesired, 39 | uint amountBDesired, 40 | uint amountAMin, 41 | uint amountBMin, 42 | address to, 43 | uint deadline 44 | ) external returns (uint amountA, uint amountB, uint liquidity); 45 | 46 | function addLiquidityETH( 47 | address token, 48 | uint amountTokenDesired, 49 | uint amountTokenMin, 50 | uint amountETHMin, 51 | address to, 52 | uint deadline 53 | ) external payable returns (uint amountToken, uint amountETH, uint liquidity); 54 | 55 | function removeLiquidity( 56 | address tokenA, 57 | address tokenB, 58 | uint liquidity, 59 | uint amountAMin, 60 | uint amountBMin, 61 | address to, 62 | uint deadline 63 | ) external returns (uint amountA, uint amountB); 64 | 65 | function removeLiquidityETH( 66 | address token, 67 | uint liquidity, 68 | uint amountTokenMin, 69 | uint amountETHMin, 70 | address to, 71 | uint deadline 72 | ) external returns (uint amountToken, uint amountETH); 73 | 74 | function removeLiquidityWithPermit( 75 | address tokenA, 76 | address tokenB, 77 | uint liquidity, 78 | uint amountAMin, 79 | uint amountBMin, 80 | address to, 81 | uint deadline, 82 | bool approveMax, uint8 v, bytes32 r, bytes32 s 83 | ) external returns (uint amountA, uint amountB); 84 | 85 | function removeLiquidityETHWithPermit( 86 | address token, 87 | uint liquidity, 88 | uint amountTokenMin, 89 | uint amountETHMin, 90 | address to, 91 | uint deadline, 92 | bool approveMax, uint8 v, bytes32 r, bytes32 s 93 | ) external returns (uint amountToken, uint amountETH); 94 | 95 | function swapExactTokensForTokens( 96 | uint amountIn, 97 | uint amountOutMin, 98 | address[] calldata path, 99 | address to, 100 | uint deadline 101 | ) external returns (uint[] memory amounts); 102 | 103 | function swapTokensForExactTokens( 104 | uint amountOut, 105 | uint amountInMax, 106 | address[] calldata path, 107 | address to, 108 | uint deadline 109 | ) external returns (uint[] memory amounts); 110 | 111 | function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) 112 | external 113 | payable 114 | returns (uint[] memory amounts); 115 | 116 | function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) 117 | external 118 | returns (uint[] memory amounts); 119 | 120 | function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) 121 | external 122 | returns (uint[] memory amounts); 123 | 124 | function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) 125 | external 126 | payable 127 | returns (uint[] memory amounts); 128 | 129 | function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); 130 | 131 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); 132 | 133 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); 134 | 135 | function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); 136 | 137 | function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); 138 | } 139 | 140 | interface IPancakeRouter02 is IPancakeRouter01 { 141 | function removeLiquidityETHSupportingFeeOnTransferTokens( 142 | address token, 143 | uint liquidity, 144 | uint amountTokenMin, 145 | uint amountETHMin, 146 | address to, 147 | uint deadline 148 | ) external returns (uint amountETH); 149 | 150 | function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( 151 | address token, 152 | uint liquidity, 153 | uint amountTokenMin, 154 | uint amountETHMin, 155 | address to, 156 | uint deadline, 157 | bool approveMax, uint8 v, bytes32 r, bytes32 s 158 | ) external returns (uint amountETH); 159 | 160 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 161 | uint amountIn, 162 | uint amountOutMin, 163 | address[] calldata path, 164 | address to, 165 | uint deadline 166 | ) external; 167 | 168 | function swapExactETHForTokensSupportingFeeOnTransferTokens( 169 | uint amountOutMin, 170 | address[] calldata path, 171 | address to, 172 | uint deadline 173 | ) external payable; 174 | 175 | function swapExactTokensForETHSupportingFeeOnTransferTokens( 176 | uint amountIn, 177 | uint amountOutMin, 178 | address[] calldata path, 179 | address to, 180 | uint deadline 181 | ) external; 182 | } 183 | 184 | interface IPancakePair { 185 | event Approval(address indexed owner, address indexed spender, uint value); 186 | event Transfer(address indexed from, address indexed to, uint value); 187 | 188 | function name() external pure returns (string memory); 189 | 190 | function symbol() external pure returns (string memory); 191 | 192 | function decimals() external pure returns (uint8); 193 | 194 | function totalSupply() external view returns (uint); 195 | 196 | function balanceOf(address owner) external view returns (uint); 197 | 198 | function allowance(address owner, address spender) external view returns (uint); 199 | 200 | function approve(address spender, uint value) external returns (bool); 201 | 202 | function transfer(address to, uint value) external returns (bool); 203 | 204 | function transferFrom(address from, address to, uint value) external returns (bool); 205 | 206 | function DOMAIN_SEPARATOR() external view returns (bytes32); 207 | 208 | function PERMIT_TYPEHASH() external pure returns (bytes32); 209 | 210 | function nonces(address owner) external view returns (uint); 211 | 212 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 213 | 214 | event Mint(address indexed sender, uint amount0, uint amount1); 215 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 216 | event Swap( 217 | address indexed sender, 218 | uint amount0In, 219 | uint amount1In, 220 | uint amount0Out, 221 | uint amount1Out, 222 | address indexed to 223 | ); 224 | event Sync(uint112 reserve0, uint112 reserve1); 225 | 226 | function MINIMUM_LIQUIDITY() external pure returns (uint); 227 | 228 | function factory() external view returns (address); 229 | 230 | function token0() external view returns (address); 231 | 232 | function token1() external view returns (address); 233 | 234 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 235 | 236 | function price0CumulativeLast() external view returns (uint); 237 | 238 | function price1CumulativeLast() external view returns (uint); 239 | 240 | function kLast() external view returns (uint); 241 | 242 | function mint(address to) external returns (uint liquidity); 243 | 244 | function burn(address to) external returns (uint amount0, uint amount1); 245 | 246 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 247 | 248 | function skim(address to) external; 249 | 250 | function sync() external; 251 | 252 | function initialize(address, address) external; 253 | } 254 | 255 | 256 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/interfaces/IUniswapV2Callee.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | interface IUniswapV2Callee { 4 | function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; 5 | } 6 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/interfaces/IUniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | interface IUniswapV2Factory { 4 | event PairCreated(address indexed token0, address indexed token1, address pair, uint); 5 | function getPair(address tokenA, address tokenB) external view returns (address pair); 6 | function allPairs(uint) external view returns (address pair); 7 | function allPairsLength() external view returns (uint); 8 | function feeTo() external view returns (address); 9 | function feeToSetter() external view returns (address); 10 | function createPair(address tokenA, address tokenB) external returns (address pair); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/interfaces/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | interface IUniswapV2Pair { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external pure returns (string memory); 8 | function symbol() external pure returns (string memory); 9 | function decimals() external pure returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | 18 | function DOMAIN_SEPARATOR() external view returns (bytes32); 19 | function PERMIT_TYPEHASH() external pure returns (bytes32); 20 | function nonces(address owner) external view returns (uint); 21 | 22 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 23 | 24 | event Mint(address indexed sender, uint amount0, uint amount1); 25 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 26 | event Swap( 27 | address indexed sender, 28 | uint amount0In, 29 | uint amount1In, 30 | uint amount0Out, 31 | uint amount1Out, 32 | address indexed to 33 | ); 34 | event Sync(uint112 reserve0, uint112 reserve1); 35 | 36 | function MINIMUM_LIQUIDITY() external pure returns (uint); 37 | function factory() external view returns (address); 38 | function token0() external view returns (address); 39 | function token1() external view returns (address); 40 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 41 | function price0CumulativeLast() external view returns (uint); 42 | function price1CumulativeLast() external view returns (uint); 43 | function kLast() external view returns (uint); 44 | 45 | function mint(address to) external returns (uint liquidity); 46 | function burn(address to) external returns (uint amount0, uint amount1); 47 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 48 | function skim(address to) external; 49 | function sync() external; 50 | 51 | function initialize(address, address) external; 52 | } 53 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/interfaces/IUniswapV2Router01.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | interface IUniswapV2Router01 { 4 | function factory() external pure returns (address); 5 | function WETH() external pure returns (address); 6 | 7 | function addLiquidity( 8 | address tokenA, 9 | address tokenB, 10 | uint amountADesired, 11 | uint amountBDesired, 12 | uint amountAMin, 13 | uint amountBMin, 14 | address to, 15 | uint deadline 16 | ) external returns (uint amountA, uint amountB, uint liquidity); 17 | function addLiquidityETH( 18 | address token, 19 | uint amountTokenDesired, 20 | uint amountTokenMin, 21 | uint amountETHMin, 22 | address to, 23 | uint deadline 24 | ) external payable returns (uint amountToken, uint amountETH, uint liquidity); 25 | function removeLiquidity( 26 | address tokenA, 27 | address tokenB, 28 | uint liquidity, 29 | uint amountAMin, 30 | uint amountBMin, 31 | address to, 32 | uint deadline 33 | ) external returns (uint amountA, uint amountB); 34 | function removeLiquidityETH( 35 | address token, 36 | uint liquidity, 37 | uint amountTokenMin, 38 | uint amountETHMin, 39 | address to, 40 | uint deadline 41 | ) external returns (uint amountToken, uint amountETH); 42 | function removeLiquidityWithPermit( 43 | address tokenA, 44 | address tokenB, 45 | uint liquidity, 46 | uint amountAMin, 47 | uint amountBMin, 48 | address to, 49 | uint deadline, 50 | bool approveMax, uint8 v, bytes32 r, bytes32 s 51 | ) external returns (uint amountA, uint amountB); 52 | function removeLiquidityETHWithPermit( 53 | address token, 54 | uint liquidity, 55 | uint amountTokenMin, 56 | uint amountETHMin, 57 | address to, 58 | uint deadline, 59 | bool approveMax, uint8 v, bytes32 r, bytes32 s 60 | ) external returns (uint amountToken, uint amountETH); 61 | function swapExactTokensForTokens( 62 | uint amountIn, 63 | uint amountOutMin, 64 | address[] calldata path, 65 | address to, 66 | uint deadline 67 | ) external returns (uint[] memory amounts); 68 | function swapTokensForExactTokens( 69 | uint amountOut, 70 | uint amountInMax, 71 | address[] calldata path, 72 | address to, 73 | uint deadline 74 | ) external returns (uint[] memory amounts); 75 | function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) 76 | external 77 | payable 78 | returns (uint[] memory amounts); 79 | function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) 80 | external 81 | returns (uint[] memory amounts); 82 | function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) 83 | external 84 | returns (uint[] memory amounts); 85 | function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) 86 | external 87 | payable 88 | returns (uint[] memory amounts); 89 | 90 | function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); 91 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); 92 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); 93 | function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); 94 | function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); 95 | } 96 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/interfaces/IUniswapV2Router02.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | import './IUniswapV2Router01.sol'; 4 | 5 | interface IUniswapV2Router02 is IUniswapV2Router01 { 6 | function removeLiquidityETHSupportingFeeOnTransferTokens( 7 | address token, 8 | uint liquidity, 9 | uint amountTokenMin, 10 | uint amountETHMin, 11 | address to, 12 | uint deadline 13 | ) external returns (uint amountETH); 14 | function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( 15 | address token, 16 | uint liquidity, 17 | uint amountTokenMin, 18 | uint amountETHMin, 19 | address to, 20 | uint deadline, 21 | bool approveMax, uint8 v, bytes32 r, bytes32 s 22 | ) external returns (uint amountETH); 23 | 24 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 25 | uint amountIn, 26 | uint amountOutMin, 27 | address[] calldata path, 28 | address to, 29 | uint deadline 30 | ) external; 31 | function swapExactETHForTokensSupportingFeeOnTransferTokens( 32 | uint amountOutMin, 33 | address[] calldata path, 34 | address to, 35 | uint deadline 36 | ) external payable; 37 | function swapExactTokensForETHSupportingFeeOnTransferTokens( 38 | uint amountIn, 39 | uint amountOutMin, 40 | address[] calldata path, 41 | address to, 42 | uint deadline 43 | ) external; 44 | } 45 | -------------------------------------------------------------------------------- /contracts/flash_loans/contracts/interfaces/IValueLiquidRouter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | /** 4 | *Submitted for verification at BscScan.com on 2021-02-23 5 | */ 6 | 7 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 8 | 9 | pragma solidity ^0.6.6; 10 | 11 | import './IERC20.sol'; 12 | 13 | 14 | interface IValueLiquidFactory { 15 | event PairCreated(address indexed token0, address indexed token1, address pair, uint32 tokenWeight0, uint32 swapFee, uint256); 16 | 17 | function feeTo() external view returns (address); 18 | 19 | function formula() external view returns (address); 20 | 21 | function protocolFee() external view returns (uint256); 22 | 23 | function feeToSetter() external view returns (address); 24 | 25 | function getPair( 26 | address tokenA, 27 | address tokenB, 28 | uint32 tokenWeightA, 29 | uint32 swapFee 30 | ) external view returns (address pair); 31 | 32 | function allPairs(uint256) external view returns (address pair); 33 | 34 | function isPair(address) external view returns (bool); 35 | 36 | function allPairsLength() external view returns (uint256); 37 | 38 | function createPair( 39 | address tokenA, 40 | address tokenB, 41 | uint32 tokenWeightA, 42 | uint32 swapFee 43 | ) external returns (address pair); 44 | 45 | function getWeightsAndSwapFee(address pair) 46 | external 47 | view 48 | returns ( 49 | uint32 tokenWeight0, 50 | uint32 tokenWeight1, 51 | uint32 swapFee 52 | ); 53 | 54 | function setFeeTo(address) external; 55 | 56 | function setFeeToSetter(address) external; 57 | 58 | function setProtocolFee(uint256) external; 59 | } 60 | 61 | /* 62 | Bancor Formula interface 63 | */ 64 | interface IValueLiquidFormula { 65 | function getReserveAndWeights(address pair, address tokenA) 66 | external 67 | view 68 | returns ( 69 | address tokenB, 70 | uint256 reserveA, 71 | uint256 reserveB, 72 | uint32 tokenWeightA, 73 | uint32 tokenWeightB, 74 | uint32 swapFee 75 | ); 76 | 77 | function getFactoryReserveAndWeights( 78 | address factory, 79 | address pair, 80 | address tokenA 81 | ) 82 | external 83 | view 84 | returns ( 85 | address tokenB, 86 | uint256 reserveA, 87 | uint256 reserveB, 88 | uint32 tokenWeightA, 89 | uint32 tokenWeightB, 90 | uint32 swapFee 91 | ); 92 | 93 | function getAmountIn( 94 | uint256 amountOut, 95 | uint256 reserveIn, 96 | uint256 reserveOut, 97 | uint32 tokenWeightIn, 98 | uint32 tokenWeightOut, 99 | uint32 swapFee 100 | ) external view returns (uint256 amountIn); 101 | 102 | function getPairAmountIn( 103 | address pair, 104 | address tokenIn, 105 | uint256 amountOut 106 | ) external view returns (uint256 amountIn); 107 | 108 | function getAmountOut( 109 | uint256 amountIn, 110 | uint256 reserveIn, 111 | uint256 reserveOut, 112 | uint32 tokenWeightIn, 113 | uint32 tokenWeightOut, 114 | uint32 swapFee 115 | ) external view returns (uint256 amountOut); 116 | 117 | function getPairAmountOut( 118 | address pair, 119 | address tokenIn, 120 | uint256 amountIn 121 | ) external view returns (uint256 amountOut); 122 | 123 | function getAmountsIn( 124 | address tokenIn, 125 | address tokenOut, 126 | uint256 amountOut, 127 | address[] calldata path 128 | ) external view returns (uint256[] memory amounts); 129 | 130 | function getFactoryAmountsIn( 131 | address factory, 132 | address tokenIn, 133 | address tokenOut, 134 | uint256 amountOut, 135 | address[] calldata path 136 | ) external view returns (uint256[] memory amounts); 137 | 138 | function getAmountsOut( 139 | address tokenIn, 140 | address tokenOut, 141 | uint256 amountIn, 142 | address[] calldata path 143 | ) external view returns (uint256[] memory amounts); 144 | 145 | function getFactoryAmountsOut( 146 | address factory, 147 | address tokenIn, 148 | address tokenOut, 149 | uint256 amountIn, 150 | address[] calldata path 151 | ) external view returns (uint256[] memory amounts); 152 | 153 | function ensureConstantValue( 154 | uint256 reserve0, 155 | uint256 reserve1, 156 | uint256 balance0Adjusted, 157 | uint256 balance1Adjusted, 158 | uint32 tokenWeight0 159 | ) external view returns (bool); 160 | 161 | function getReserves( 162 | address pair, 163 | address tokenA, 164 | address tokenB 165 | ) external view returns (uint256 reserveA, uint256 reserveB); 166 | 167 | function getOtherToken(address pair, address tokenA) external view returns (address tokenB); 168 | 169 | function quote( 170 | uint256 amountA, 171 | uint256 reserveA, 172 | uint256 reserveB 173 | ) external pure returns (uint256 amountB); 174 | 175 | function sortTokens(address tokenA, address tokenB) external pure returns (address token0, address token1); 176 | 177 | function mintLiquidityFee( 178 | uint256 totalLiquidity, 179 | uint112 reserve0, 180 | uint112 reserve1, 181 | uint32 tokenWeight0, 182 | uint32 tokenWeight1, 183 | uint112 collectedFee0, 184 | uint112 collectedFee1 185 | ) external view returns (uint256 amount); 186 | } 187 | 188 | interface IValueLiquidPair { 189 | event Approval(address indexed owner, address indexed spender, uint256 value); 190 | event Transfer(address indexed from, address indexed to, uint256 value); 191 | 192 | function name() external view returns (string memory); 193 | 194 | function symbol() external view returns (string memory); 195 | 196 | function decimals() external pure returns (uint8); 197 | 198 | function totalSupply() external view returns (uint256); 199 | 200 | function balanceOf(address owner) external view returns (uint256); 201 | 202 | function allowance(address owner, address spender) external view returns (uint256); 203 | 204 | function approve(address spender, uint256 value) external returns (bool); 205 | 206 | function transfer(address to, uint256 value) external returns (bool); 207 | 208 | function transferFrom( 209 | address from, 210 | address to, 211 | uint256 value 212 | ) external returns (bool); 213 | 214 | function DOMAIN_SEPARATOR() external view returns (bytes32); 215 | 216 | function PERMIT_TYPEHASH() external pure returns (bytes32); 217 | 218 | function nonces(address owner) external view returns (uint256); 219 | 220 | function permit( 221 | address owner, 222 | address spender, 223 | uint256 value, 224 | uint256 deadline, 225 | uint8 v, 226 | bytes32 r, 227 | bytes32 s 228 | ) external; 229 | 230 | event PaidProtocolFee(uint112 collectedFee0, uint112 collectedFee1); 231 | event Mint(address indexed sender, uint256 amount0, uint256 amount1); 232 | event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); 233 | event Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to); 234 | event Sync(uint112 reserve0, uint112 reserve1); 235 | 236 | function MINIMUM_LIQUIDITY() external pure returns (uint256); 237 | 238 | function factory() external view returns (address); 239 | 240 | function token0() external view returns (address); 241 | 242 | function token1() external view returns (address); 243 | 244 | function getReserves() 245 | external 246 | view 247 | returns ( 248 | uint112 reserve0, 249 | uint112 reserve1, 250 | uint32 blockTimestampLast 251 | ); 252 | 253 | function getCollectedFees() external view returns (uint112 _collectedFee0, uint112 _collectedFee1); 254 | 255 | function getTokenWeights() external view returns (uint32 tokenWeight0, uint32 tokenWeight1); 256 | 257 | function getSwapFee() external view returns (uint32); 258 | 259 | function price0CumulativeLast() external view returns (uint256); 260 | 261 | function price1CumulativeLast() external view returns (uint256); 262 | 263 | function mint(address to) external returns (uint256 liquidity); 264 | 265 | function burn(address to) external returns (uint256 amount0, uint256 amount1); 266 | 267 | function swap( 268 | uint256 amount0Out, 269 | uint256 amount1Out, 270 | address to, 271 | bytes calldata data 272 | ) external; 273 | 274 | function skim(address to) external; 275 | 276 | function sync() external; 277 | 278 | function initialize( 279 | address, 280 | address, 281 | uint32, 282 | uint32 283 | ) external; 284 | } 285 | 286 | interface IValueLiquidRouter { 287 | event Exchange(address pair, uint256 amountOut, address output); 288 | struct Swap { 289 | address pool; 290 | address tokenIn; 291 | address tokenOut; 292 | uint256 swapAmount; // tokenInAmount / tokenOutAmount 293 | uint256 limitReturnAmount; // minAmountOut / maxAmountIn 294 | uint256 maxPrice; 295 | } 296 | 297 | function factory() external view returns (address); 298 | 299 | function controller() external view returns (address); 300 | 301 | function formula() external view returns (address); 302 | 303 | function WETH() external view returns (address); 304 | 305 | function addLiquidity( 306 | address pair, 307 | address tokenA, 308 | address tokenB, 309 | uint256 amountADesired, 310 | uint256 amountBDesired, 311 | uint256 amountAMin, 312 | uint256 amountBMin, 313 | address to, 314 | uint256 deadline 315 | ) 316 | external 317 | returns ( 318 | uint256 amountA, 319 | uint256 amountB, 320 | uint256 liquidity 321 | ); 322 | 323 | function addLiquidityETH( 324 | address pair, 325 | address token, 326 | uint256 amountTokenDesired, 327 | uint256 amountTokenMin, 328 | uint256 amountETHMin, 329 | address to, 330 | uint256 deadline 331 | ) 332 | external 333 | payable 334 | returns ( 335 | uint256 amountToken, 336 | uint256 amountETH, 337 | uint256 liquidity 338 | ); 339 | 340 | function swapExactTokensForTokens( 341 | address tokenIn, 342 | address tokenOut, 343 | uint256 amountIn, 344 | uint256 amountOutMin, 345 | address[] calldata path, 346 | address to, 347 | uint256 deadline 348 | ) external returns (uint256[] memory amounts); 349 | 350 | function swapTokensForExactTokens( 351 | address tokenIn, 352 | address tokenOut, 353 | uint256 amountOut, 354 | uint256 amountInMax, 355 | address[] calldata path, 356 | address to, 357 | uint256 deadline 358 | ) external returns (uint256[] memory amounts); 359 | 360 | function swapExactETHForTokens( 361 | address tokenOut, 362 | uint256 amountOutMin, 363 | address[] calldata path, 364 | address to, 365 | uint256 deadline 366 | ) external payable returns (uint256[] memory amounts); 367 | 368 | function swapTokensForExactETH( 369 | address tokenIn, 370 | uint256 amountOut, 371 | uint256 amountInMax, 372 | address[] calldata path, 373 | address to, 374 | uint256 deadline 375 | ) external returns (uint256[] memory amounts); 376 | 377 | function swapExactTokensForETH( 378 | address tokenIn, 379 | uint256 amountIn, 380 | uint256 amountOutMin, 381 | address[] calldata path, 382 | address to, 383 | uint256 deadline 384 | ) external returns (uint256[] memory amounts); 385 | 386 | function swapETHForExactTokens( 387 | address tokenOut, 388 | uint256 amountOut, 389 | address[] calldata path, 390 | address to, 391 | uint256 deadline 392 | ) external payable returns (uint256[] memory amounts); 393 | 394 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 395 | address tokenIn, 396 | address tokenOut, 397 | uint256 amountIn, 398 | uint256 amountOutMin, 399 | address[] calldata path, 400 | address to, 401 | uint256 deadline 402 | ) external; 403 | 404 | function swapExactETHForTokensSupportingFeeOnTransferTokens( 405 | address tokenOut, 406 | uint256 amountOutMin, 407 | address[] calldata path, 408 | address to, 409 | uint256 deadline 410 | ) external payable; 411 | 412 | function swapExactTokensForETHSupportingFeeOnTransferTokens( 413 | address tokenIn, 414 | uint256 amountIn, 415 | uint256 amountOutMin, 416 | address[] calldata path, 417 | address to, 418 | uint256 deadline 419 | ) external; 420 | 421 | /*function multihopBatchSwapExactIn( 422 | Swap[][] memory swapSequences, 423 | address tokenIn, 424 | address tokenOut, 425 | uint256 totalAmountIn, 426 | uint256 minTotalAmountOut, 427 | uint256 deadline 428 | ) external payable returns (uint256 totalAmountOut); 429 | 430 | function multihopBatchSwapExactOut( 431 | Swap[][] memory swapSequences, 432 | address tokenIn, 433 | address tokenOut, 434 | uint256 maxTotalAmountIn, 435 | uint256 deadline 436 | ) external payable returns (uint256 totalAmountIn);*/ 437 | 438 | function createPair( 439 | address tokenA, 440 | address tokenB, 441 | uint256 amountA, 442 | uint256 amountB, 443 | uint32 tokenWeightA, 444 | uint32 swapFee, 445 | address to 446 | ) external returns (uint256 liquidity); 447 | 448 | function createPairETH( 449 | address token, 450 | uint256 amountToken, 451 | uint32 tokenWeight, 452 | uint32 swapFee, 453 | address to 454 | ) external payable returns (uint256 liquidity); 455 | 456 | function removeLiquidity( 457 | address pair, 458 | address tokenA, 459 | address tokenB, 460 | uint256 liquidity, 461 | uint256 amountAMin, 462 | uint256 amountBMin, 463 | address to, 464 | uint256 deadline 465 | ) external returns (uint256 amountA, uint256 amountB); 466 | 467 | function removeLiquidityETH( 468 | address pair, 469 | address token, 470 | uint256 liquidity, 471 | uint256 amountTokenMin, 472 | uint256 amountETHMin, 473 | address to, 474 | uint256 deadline 475 | ) external returns (uint256 amountToken, uint256 amountETH); 476 | 477 | function removeLiquidityWithPermit( 478 | address pair, 479 | address tokenA, 480 | address tokenB, 481 | uint256 liquidity, 482 | uint256 amountAMin, 483 | uint256 amountBMin, 484 | address to, 485 | uint256 deadline, 486 | bool approveMax, 487 | uint8 v, 488 | bytes32 r, 489 | bytes32 s 490 | ) external returns (uint256 amountA, uint256 amountB); 491 | 492 | function removeLiquidityETHWithPermit( 493 | address pair, 494 | address token, 495 | uint256 liquidity, 496 | uint256 amountTokenMin, 497 | uint256 amountETHMin, 498 | address to, 499 | uint256 deadline, 500 | bool approveMax, 501 | uint8 v, 502 | bytes32 r, 503 | bytes32 s 504 | ) external returns (uint256 amountToken, uint256 amountETH); 505 | 506 | function removeLiquidityETHSupportingFeeOnTransferTokens( 507 | address pair, 508 | address token, 509 | uint256 liquidity, 510 | uint256 amountTokenMin, 511 | uint256 amountETHMin, 512 | address to, 513 | uint256 deadline 514 | ) external returns (uint256 amountETH); 515 | 516 | function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( 517 | address pair, 518 | address token, 519 | uint256 liquidity, 520 | uint256 amountTokenMin, 521 | uint256 amountETHMin, 522 | address to, 523 | uint256 deadline, 524 | bool approveMax, 525 | uint8 v, 526 | bytes32 r, 527 | bytes32 s 528 | ) external returns (uint256 amountETH); 529 | } 530 | 531 | interface IBPool is IERC20 { 532 | function version() external view returns (uint256); 533 | 534 | function swapExactAmountIn( 535 | address, 536 | uint256, 537 | address, 538 | uint256, 539 | uint256 540 | ) external returns (uint256, uint256); 541 | 542 | function swapExactAmountOut( 543 | address, 544 | uint256, 545 | address, 546 | uint256, 547 | uint256 548 | ) external returns (uint256, uint256); 549 | 550 | function calcInGivenOut( 551 | uint256, 552 | uint256, 553 | uint256, 554 | uint256, 555 | uint256, 556 | uint256 557 | ) external pure returns (uint256); 558 | 559 | function calcOutGivenIn( 560 | uint256, 561 | uint256, 562 | uint256, 563 | uint256, 564 | uint256, 565 | uint256 566 | ) external pure returns (uint256); 567 | 568 | function getDenormalizedWeight(address) external view returns (uint256); 569 | 570 | function swapFee() external view returns (uint256); 571 | 572 | function setSwapFee(uint256 _swapFee) external; 573 | 574 | function bind( 575 | address token, 576 | uint256 balance, 577 | uint256 denorm 578 | ) external; 579 | 580 | function rebind( 581 | address token, 582 | uint256 balance, 583 | uint256 denorm 584 | ) external; 585 | 586 | function finalize( 587 | uint256 _swapFee, 588 | uint256 _initPoolSupply, 589 | address[] calldata _bindTokens, 590 | uint256[] calldata _bindDenorms 591 | ) external; 592 | 593 | function setPublicSwap(bool _publicSwap) external; 594 | 595 | function setController(address _controller) external; 596 | 597 | function setExchangeProxy(address _exchangeProxy) external; 598 | 599 | function getFinalTokens() external view returns (address[] memory tokens); 600 | 601 | function getTotalDenormalizedWeight() external view returns (uint256); 602 | 603 | function getBalance(address token) external view returns (uint256); 604 | 605 | function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) external; 606 | 607 | function joinPoolFor( 608 | address account, 609 | uint256 rewardAmountOut, 610 | uint256[] calldata maxAmountsIn 611 | ) external; 612 | 613 | function joinswapPoolAmountOut( 614 | address tokenIn, 615 | uint256 poolAmountOut, 616 | uint256 maxAmountIn 617 | ) external returns (uint256 tokenAmountIn); 618 | 619 | function exitPool(uint256 poolAmountIn, uint256[] calldata minAmountsOut) external; 620 | 621 | function exitswapPoolAmountIn( 622 | address tokenOut, 623 | uint256 poolAmountIn, 624 | uint256 minAmountOut 625 | ) external returns (uint256 tokenAmountOut); 626 | 627 | function exitswapExternAmountOut( 628 | address tokenOut, 629 | uint256 tokenAmountOut, 630 | uint256 maxPoolAmountIn 631 | ) external returns (uint256 poolAmountIn); 632 | 633 | function joinswapExternAmountIn( 634 | address tokenIn, 635 | uint256 tokenAmountIn, 636 | uint256 minPoolAmountOut 637 | ) external returns (uint256 poolAmountOut); 638 | 639 | function finalizeRewardFundInfo(address _rewardFund, uint256 _unstakingFrozenTime) external; 640 | 641 | function addRewardPool( 642 | IERC20 _rewardToken, 643 | uint256 _startBlock, 644 | uint256 _endRewardBlock, 645 | uint256 _rewardPerBlock, 646 | uint256 _lockRewardPercent, 647 | uint256 _startVestingBlock, 648 | uint256 _endVestingBlock 649 | ) external; 650 | 651 | function isBound(address t) external view returns (bool); 652 | 653 | function getSpotPrice(address tokenIn, address tokenOut) external view returns (uint256 spotPrice); 654 | } 655 | -------------------------------------------------------------------------------- /contracts/flash_loans/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /contracts/flash_loans/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const Arbitrage = artifacts.require("Arbitrage.sol"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy( 5 | Arbitrage, 6 | '0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F', //PancakeSwap router 7 | "0xcde540d7eafe93ac5fe6233bee57e1270d3e330f", 8 | "0xb7e19a1188776f32e8c2b790d9ca578f2896da7c", 9 | "0xC0788A3aD43d79aa53B09c2EaCc313A787d1d607", 10 | "0xBCfCcbde45cE874adCB698cC183deBcF17952812" //PancakeSwap factory 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /contracts/flash_loans/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "298-arbitrage-uniswap-sushiswap", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle-config.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "@pancakeswap-libs/pancake-swap-core": "^0.1.0", 11 | "pancakeswap-peripheral": "^1.1.0-beta.0", 12 | "zeppelin-solidity": "^1.12.0" 13 | }, 14 | "devDependencies": { 15 | "@openzeppelin/contracts": "^3.4.1", 16 | "@openzeppelin/test-environment": "^0.1.9", 17 | "@truffle/hdwallet-provider": "^1.2.6", 18 | "chai": "^4.3.4", 19 | "mocha": "^8.3.2" 20 | }, 21 | "scripts": { 22 | "test": "echo \"Error: no test specified\" && exit 1" 23 | }, 24 | "keywords": [], 25 | "author": "", 26 | "license": "ISC" 27 | } 28 | -------------------------------------------------------------------------------- /contracts/flash_loans/test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niccoloCastelli/defiarb/dee77e494588c076b47cfc450ec7abef6064c00f/contracts/flash_loans/test/.gitkeep -------------------------------------------------------------------------------- /contracts/flash_loans/test/testArbitrage.js: -------------------------------------------------------------------------------- 1 | const { accounts, contract } = require('@openzeppelin/test-environment'); 2 | const [ owner ] = accounts; 3 | 4 | const { expect } = require('chai'); 5 | 6 | const ArbitrageContract = contract.fromArtifact('Arbitrage'); // Loads a compiled contract 7 | 8 | describe('Arbitrage', function () { 9 | this.timeout(15000); 10 | 11 | beforeEach(async () => { 12 | this.arbContract = await ArbitrageContract.new({ from: owner }); 13 | }) 14 | 15 | it('call arbitrage', async () => { 16 | const env = { 17 | router1: '', 18 | router2: '', 19 | token0: '', 20 | token1: '', 21 | token2: '', 22 | amount0: '', 23 | amount1: '', 24 | amount2: '', 25 | } 26 | 27 | await this.arbContract.startArbitrage("0xBCfCcbde45cE874adCB698cC183deBcF17952812","0xBCfCcbde45cE874adCB698cC183deBcF17952812","0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c","0x55d398326f99059fF775485246999027B3197955","0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3","0","1000000000000000000000","997376987535316000000") 28 | // expect().to.equal(owner); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /contracts/flash_loans/truffle-config.js: -------------------------------------------------------------------------------- 1 | const HDWalletProvider = require('@truffle/hdwallet-provider'); 2 | const fs = require('fs'); 3 | const mnemonic = fs.readFileSync(".secret").toString().trim(); 4 | 5 | /** 6 | * Use this file to configure your truffle project. It's seeded with some 7 | * common settings for different networks and features like migrations, 8 | * compilation and testing. Uncomment the ones you need or modify 9 | * them to suit your project as necessary. 10 | * 11 | * More information about configuration can be found at: 12 | * 13 | * trufflesuite.com/docs/advanced/configuration 14 | * 15 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 16 | * to sign your transactions before they're sent to a remote public node. Infura accounts 17 | * are available for free at: infura.io/register. 18 | * 19 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 20 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 21 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 22 | * 23 | */ 24 | 25 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 26 | // const infuraKey = "fj4jll3k....."; 27 | // 28 | // const fs = require('fs'); 29 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 30 | 31 | module.exports = { 32 | /** 33 | * Networks define how you connect to your ethereum client and let you set the 34 | * defaults web3 uses to send transactions. If you don't specify one truffle 35 | * will spin up a development blockchain for you on port 9545 when you 36 | * run `develop` or `test`. You can ask a truffle command to use a specific 37 | * network from the command line, e.g 38 | * 39 | * $ truffle test --network 40 | */ 41 | 42 | networks: { 43 | // Useful for testing. The `development` name is special - truffle uses it by default 44 | // if it's defined here and no other network is specified at the command line. 45 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 46 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 47 | // options below to some value. 48 | // 49 | development: { 50 | gasPrice: 10000000000, 51 | host: "127.0.0.1", // Localhost (default: none) 52 | port: 8545, // Standard Ethereum port (default: none) 53 | network_id: "*", // Any network (default: none) 54 | }, 55 | testnet: { //truffle migrate --network testnet 56 | provider: () => new HDWalletProvider(mnemonic, `wss://data-seed-prebsc-1-s1.binance.org:8545`), 57 | network_id: 97, 58 | confirmations: 5, 59 | timeoutBlocks: 1800, 60 | gasPrice: 10000000000, 61 | skipDryRun: true, 62 | networkCheckTimeout: 1000000, 63 | }, 64 | bsc: { //truffle migrate --network bsc 65 | provider: () => new HDWalletProvider(mnemonic, `wss://bsc-ws-node.nariox.org:443`), 66 | network_id: 56, 67 | confirmations: 5, 68 | timeoutBlocks: 1800, 69 | gasPrice: 5000000000, 70 | skipDryRun: false 71 | }, 72 | }, 73 | 74 | // Set default mocha options here, use special reporters etc. 75 | mocha: { 76 | // timeout: 100000 77 | }, 78 | 79 | // Configure your compilers 80 | compilers: { 81 | solc: { 82 | version: "0.6.6", // Fetch exact version from solc-bin (default: truffle's version) 83 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 84 | // settings: { // See the solidity docs for advice about optimization and evmVersion 85 | // optimizer: { 86 | // enabled: false, 87 | // runs: 200 88 | // }, 89 | // evmVersion: "byzantium" 90 | // } 91 | } 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /contracts/src/IBEP20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.3; 2 | 3 | interface IBEP20 { 4 | /** 5 | * @dev Returns the amount of tokens in existence. 6 | */ 7 | function totalSupply() external view returns (uint256); 8 | 9 | /** 10 | * @dev Returns the token decimals. 11 | */ 12 | function decimals() external view returns (uint8); 13 | 14 | /** 15 | * @dev Returns the token symbol. 16 | */ 17 | function symbol() external view returns (string memory); 18 | 19 | /** 20 | * @dev Returns the token name. 21 | */ 22 | function name() external view returns (string memory); 23 | 24 | /** 25 | * @dev Returns the bep token owner. 26 | */ 27 | function getOwner() external view returns (address); 28 | 29 | /** 30 | * @dev Returns the amount of tokens owned by `account`. 31 | */ 32 | function balanceOf(address account) external view returns (uint256); 33 | 34 | /** 35 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 36 | * 37 | * Returns a boolean value indicating whether the operation succeeded. 38 | * 39 | * Emits a {Transfer} event. 40 | */ 41 | function transfer(address recipient, uint256 amount) external returns (bool); 42 | 43 | /** 44 | * @dev Returns the remaining number of tokens that `spender` will be 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 46 | * zero by default. 47 | * 48 | * This value changes when {approve} or {transferFrom} are called. 49 | */ 50 | function allowance(address _owner, address spender) external view returns (uint256); 51 | 52 | /** 53 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 54 | * 55 | * Returns a boolean value indicating whether the operation succeeded. 56 | * 57 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 58 | * that someone may use both the old and the new allowance by unfortunate 59 | * transaction ordering. One possible solution to mitigate this race 60 | * condition is to first reduce the spender's allowance to 0 and set the 61 | * desired value afterwards: 62 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 63 | * 64 | * Emits an {Approval} event. 65 | */ 66 | function approve(address spender, uint256 amount) external returns (bool); 67 | 68 | /** 69 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 70 | * allowance mechanism. `amount` is then deducted from the caller's 71 | * allowance. 72 | * 73 | * Returns a boolean value indicating whether the operation succeeded. 74 | * 75 | * Emits a {Transfer} event. 76 | */ 77 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 78 | 79 | /** 80 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 81 | * another (`to`). 82 | * 83 | * Note that `value` may be zero. 84 | */ 85 | event Transfer(address indexed from, address indexed to, uint256 value); 86 | 87 | /** 88 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 89 | * a call to {approve}. `value` is the new allowance. 90 | */ 91 | event Approval(address indexed owner, address indexed spender, uint256 value); 92 | } -------------------------------------------------------------------------------- /exchanges/apeswap/apeswap.go: -------------------------------------------------------------------------------- 1 | package apeswap 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/ethclient" 5 | "github.com/niccoloCastelli/defiarb/exchanges" 6 | "github.com/niccoloCastelli/defiarb/exchanges/pancake" 7 | ) 8 | 9 | const ( 10 | routerAddress = "0xC0788A3aD43d79aa53B09c2EaCc313A787d1d607" 11 | Name = "apeswap" 12 | ) 13 | 14 | func init() { 15 | exchanges.RegisterExchange(Name, NewRouter) 16 | } 17 | 18 | func NewRouter(client *ethclient.Client, address string) (exchanges.Router, error) { 19 | return pancake.NewRouterWithName(client, routerAddress, Name) 20 | } 21 | -------------------------------------------------------------------------------- /exchanges/bakery/mdex.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/ethclient" 5 | "github.com/niccoloCastelli/defiarb/exchanges" 6 | "github.com/niccoloCastelli/defiarb/exchanges/pancake" 7 | ) 8 | 9 | const ( 10 | routerAddress = "0xcde540d7eafe93ac5fe6233bee57e1270d3e330f" 11 | name = "bakery" 12 | ) 13 | 14 | func init() { 15 | exchanges.RegisterExchange(name, NewRouter) 16 | } 17 | 18 | func NewRouter(client *ethclient.Client, address string) (exchanges.Router, error) { 19 | return pancake.NewRouterWithName(client, routerAddress, name) 20 | } 21 | -------------------------------------------------------------------------------- /exchanges/main.go: -------------------------------------------------------------------------------- 1 | package exchanges 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/ethclient" 9 | "github.com/niccoloCastelli/defiarb/storage/models" 10 | "github.com/pkg/errors" 11 | "math/big" 12 | ) 13 | 14 | type RouterConstructor func(client *ethclient.Client, address string) (Router, error) 15 | 16 | var registry = map[string]RouterConstructor{} 17 | 18 | func RegisterExchange(name string, constructor RouterConstructor) { 19 | registry[name] = constructor 20 | } 21 | 22 | func New(name string, client *ethclient.Client, address string) (Router, error) { 23 | c, ok := registry[name] 24 | if !ok { 25 | return nil, errors.New("exchange not found") 26 | } 27 | return c(client, address) 28 | } 29 | func Registered() []string { 30 | ret := make([]string, 0, len(registry)) 31 | for key := range registry { 32 | ret = append(ret, key) 33 | } 34 | return ret 35 | } 36 | 37 | type Router interface { 38 | Name() string 39 | GetRouterAddress() common.Address 40 | GetFactoryAddress() common.Address 41 | GetRouterContract() RouterContract 42 | GetAllPairs(ctx context.Context) ([]common.Address, error) 43 | GetAllPairsAsync(ctx context.Context) (int64, chan *common.Address, chan error) 44 | 45 | GetPairInfo(address common.Address) (*models.LiquidityPool, error) 46 | GetPairPrice(address common.Address, token0Amount int64, token1Amount int64) (*TradeInfo, error) 47 | GetReserves(address common.Address) (float64, float64, error) 48 | } 49 | 50 | type RouterContract interface { 51 | // GetAmountIn(opts *bind.CallOpts, amountOut *big.Int, reserveIn *big.Int, reserveOut *big.Int) (*big.Int, error) 52 | // GetAmountOut(opts *bind.CallOpts, amountIn *big.Int, reserveIn *big.Int, reserveOut *big.Int) (*big.Int, error) 53 | GetAmountsIn(opts *bind.CallOpts, amountOut *big.Int, path []common.Address) ([]*big.Int, error) 54 | GetAmountsOut(opts *bind.CallOpts, amountIn *big.Int, path []common.Address) ([]*big.Int, error) 55 | // Quote(opts *bind.CallOpts, amountA *big.Int, reserveA *big.Int, reserveB *big.Int) (*big.Int, error) 56 | } 57 | 58 | type TradeInfo struct { 59 | Token0Price float64 60 | Token1Price float64 61 | } 62 | 63 | func (t TradeInfo) String() string { 64 | return fmt.Sprintf("%f %f (slippage: %f)", t.Token0Price, t.Token1Price, t.Slippage()) 65 | } 66 | func (t TradeInfo) Slippage() float64 { 67 | return (1/t.Token1Price - t.Token0Price) / t.Token0Price 68 | } 69 | -------------------------------------------------------------------------------- /exchanges/mdex/mdex.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/ethclient" 5 | "github.com/niccoloCastelli/defiarb/exchanges" 6 | "github.com/niccoloCastelli/defiarb/exchanges/pancake" 7 | ) 8 | 9 | const ( 10 | routerAddress = "0x7DAe51BD3E3376B8c7c4900E9107f12Be3AF1bA8" 11 | name = "mdex" 12 | ) 13 | 14 | func init() { 15 | exchanges.RegisterExchange(name, NewRouter) 16 | } 17 | 18 | func NewRouter(client *ethclient.Client, address string) (exchanges.Router, error) { 19 | return pancake.NewRouterWithName(client, routerAddress, name) 20 | } 21 | -------------------------------------------------------------------------------- /exchanges/pancake/build.sh: -------------------------------------------------------------------------------- 1 | WD=$PWD 2 | cd contracts || exit 3 | mkdir -p build 4 | mkdir -p factory 5 | mkdir -p router 6 | mkdir -p pair 7 | solc --overwrite --abi --bin ./src/PancakeRouter.sol -o build 8 | abigen --bin=./build/IPancakeFactory.bin --abi=./build/IPancakeFactory.abi --pkg=factory --out=./factory/IPancakeFactory.go 9 | abigen --bin=./build/IPancakeRouter01.bin --abi=./build/IPancakeRouter01.abi --pkg=router --out=./router/IPancakeRouter01.go 10 | abigen --bin=./build/IPancakePair.bin --abi=./build/IPancakePair.abi --pkg=pair --out=./pair/IPancakePair.go 11 | #abigen --bin=./build/IPancakeRouter02.bin --abi=./build/IPancakeRouter02.abi --pkg=contracts --out=./IPancakeRouter02.go 12 | 13 | #abigen --bin=./build/IERC20.bin --abi=./build/IERC20.abi --pkg=contracts --out=./IERC20.go 14 | #abigen --bin=./build/IWETH.bin --abi=./build/IWETH.abi --pkg=contracts --out=./IWETH.go 15 | cd $WD || exit -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IERC20.abi: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IERC20.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niccoloCastelli/defiarb/dee77e494588c076b47cfc450ec7abef6064c00f/exchanges/pancake/contracts/build/IERC20.bin -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IPancakeFactory.abi: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token0","type":"address"},{"indexed":true,"internalType":"address","name":"token1","type":"address"},{"indexed":false,"internalType":"address","name":"pair","type":"address"},{"indexed":false,"internalType":"uint256","name":"","type":"uint256"}],"name":"PairCreated","type":"event"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allPairs","outputs":[{"internalType":"address","name":"pair","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allPairsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"createPair","outputs":[{"internalType":"address","name":"pair","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeToSetter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"pair","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"setFeeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"setFeeToSetter","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IPancakeFactory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niccoloCastelli/defiarb/dee77e494588c076b47cfc450ec7abef6064c00f/exchanges/pancake/contracts/build/IPancakeFactory.bin -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IPancakePair.abi: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount0Out","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1Out","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint112","name":"reserve0","type":"uint112"},{"indexed":false,"internalType":"uint112","name":"reserve1","type":"uint112"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"reserve0","type":"uint112"},{"internalType":"uint112","name":"reserve1","type":"uint112"},{"internalType":"uint32","name":"blockTimestampLast","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"kLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"price0CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price1CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount0Out","type":"uint256"},{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"sync","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IPancakePair.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niccoloCastelli/defiarb/dee77e494588c076b47cfc450ec7abef6064c00f/exchanges/pancake/contracts/build/IPancakePair.bin -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IPancakeRouter01.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermit","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityWithPermit","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapETHForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IPancakeRouter01.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niccoloCastelli/defiarb/dee77e494588c076b47cfc450ec7abef6064c00f/exchanges/pancake/contracts/build/IPancakeRouter01.bin -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IPancakeRouter02.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETHSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermit","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermitSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityWithPermit","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapETHForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETHSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IPancakeRouter02.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niccoloCastelli/defiarb/dee77e494588c076b47cfc450ec7abef6064c00f/exchanges/pancake/contracts/build/IPancakeRouter02.bin -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IWETH.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /exchanges/pancake/contracts/build/IWETH.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niccoloCastelli/defiarb/dee77e494588c076b47cfc450ec7abef6064c00f/exchanges/pancake/contracts/build/IWETH.bin -------------------------------------------------------------------------------- /exchanges/pancake/contracts/src/PancakeRouter.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at BscScan.com on 2020-09-19 3 | */ 4 | 5 | pragma solidity =0.8.3; 6 | 7 | 8 | interface IPancakeFactory { 9 | event PairCreated(address indexed token0, address indexed token1, address pair, uint); 10 | 11 | function feeTo() external view returns (address); 12 | 13 | function feeToSetter() external view returns (address); 14 | 15 | function getPair(address tokenA, address tokenB) external view returns (address pair); 16 | 17 | function allPairs(uint) external view returns (address pair); 18 | 19 | function allPairsLength() external view returns (uint); 20 | 21 | function createPair(address tokenA, address tokenB) external returns (address pair); 22 | 23 | function setFeeTo(address) external; 24 | 25 | function setFeeToSetter(address) external; 26 | } 27 | 28 | interface IPancakeRouter01 { 29 | function factory() external pure returns (address); 30 | 31 | function WETH() external pure returns (address); 32 | 33 | function addLiquidity( 34 | address tokenA, 35 | address tokenB, 36 | uint amountADesired, 37 | uint amountBDesired, 38 | uint amountAMin, 39 | uint amountBMin, 40 | address to, 41 | uint deadline 42 | ) external returns (uint amountA, uint amountB, uint liquidity); 43 | 44 | function addLiquidityETH( 45 | address token, 46 | uint amountTokenDesired, 47 | uint amountTokenMin, 48 | uint amountETHMin, 49 | address to, 50 | uint deadline 51 | ) external payable returns (uint amountToken, uint amountETH, uint liquidity); 52 | 53 | function removeLiquidity( 54 | address tokenA, 55 | address tokenB, 56 | uint liquidity, 57 | uint amountAMin, 58 | uint amountBMin, 59 | address to, 60 | uint deadline 61 | ) external returns (uint amountA, uint amountB); 62 | 63 | function removeLiquidityETH( 64 | address token, 65 | uint liquidity, 66 | uint amountTokenMin, 67 | uint amountETHMin, 68 | address to, 69 | uint deadline 70 | ) external returns (uint amountToken, uint amountETH); 71 | 72 | function removeLiquidityWithPermit( 73 | address tokenA, 74 | address tokenB, 75 | uint liquidity, 76 | uint amountAMin, 77 | uint amountBMin, 78 | address to, 79 | uint deadline, 80 | bool approveMax, uint8 v, bytes32 r, bytes32 s 81 | ) external returns (uint amountA, uint amountB); 82 | 83 | function removeLiquidityETHWithPermit( 84 | address token, 85 | uint liquidity, 86 | uint amountTokenMin, 87 | uint amountETHMin, 88 | address to, 89 | uint deadline, 90 | bool approveMax, uint8 v, bytes32 r, bytes32 s 91 | ) external returns (uint amountToken, uint amountETH); 92 | 93 | function swapExactTokensForTokens( 94 | uint amountIn, 95 | uint amountOutMin, 96 | address[] calldata path, 97 | address to, 98 | uint deadline 99 | ) external returns (uint[] memory amounts); 100 | 101 | function swapTokensForExactTokens( 102 | uint amountOut, 103 | uint amountInMax, 104 | address[] calldata path, 105 | address to, 106 | uint deadline 107 | ) external returns (uint[] memory amounts); 108 | 109 | function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) 110 | external 111 | payable 112 | returns (uint[] memory amounts); 113 | 114 | function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) 115 | external 116 | returns (uint[] memory amounts); 117 | 118 | function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) 119 | external 120 | returns (uint[] memory amounts); 121 | 122 | function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) 123 | external 124 | payable 125 | returns (uint[] memory amounts); 126 | 127 | function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); 128 | 129 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); 130 | 131 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); 132 | 133 | function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); 134 | 135 | function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); 136 | } 137 | 138 | interface IPancakeRouter02 is IPancakeRouter01 { 139 | function removeLiquidityETHSupportingFeeOnTransferTokens( 140 | address token, 141 | uint liquidity, 142 | uint amountTokenMin, 143 | uint amountETHMin, 144 | address to, 145 | uint deadline 146 | ) external returns (uint amountETH); 147 | 148 | function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( 149 | address token, 150 | uint liquidity, 151 | uint amountTokenMin, 152 | uint amountETHMin, 153 | address to, 154 | uint deadline, 155 | bool approveMax, uint8 v, bytes32 r, bytes32 s 156 | ) external returns (uint amountETH); 157 | 158 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 159 | uint amountIn, 160 | uint amountOutMin, 161 | address[] calldata path, 162 | address to, 163 | uint deadline 164 | ) external; 165 | 166 | function swapExactETHForTokensSupportingFeeOnTransferTokens( 167 | uint amountOutMin, 168 | address[] calldata path, 169 | address to, 170 | uint deadline 171 | ) external payable; 172 | 173 | function swapExactTokensForETHSupportingFeeOnTransferTokens( 174 | uint amountIn, 175 | uint amountOutMin, 176 | address[] calldata path, 177 | address to, 178 | uint deadline 179 | ) external; 180 | } 181 | 182 | interface IPancakePair { 183 | event Approval(address indexed owner, address indexed spender, uint value); 184 | event Transfer(address indexed from, address indexed to, uint value); 185 | 186 | function name() external pure returns (string memory); 187 | 188 | function symbol() external pure returns (string memory); 189 | 190 | function decimals() external pure returns (uint8); 191 | 192 | function totalSupply() external view returns (uint); 193 | 194 | function balanceOf(address owner) external view returns (uint); 195 | 196 | function allowance(address owner, address spender) external view returns (uint); 197 | 198 | function approve(address spender, uint value) external returns (bool); 199 | 200 | function transfer(address to, uint value) external returns (bool); 201 | 202 | function transferFrom(address from, address to, uint value) external returns (bool); 203 | 204 | function DOMAIN_SEPARATOR() external view returns (bytes32); 205 | 206 | function PERMIT_TYPEHASH() external pure returns (bytes32); 207 | 208 | function nonces(address owner) external view returns (uint); 209 | 210 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 211 | 212 | event Mint(address indexed sender, uint amount0, uint amount1); 213 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 214 | event Swap( 215 | address indexed sender, 216 | uint amount0In, 217 | uint amount1In, 218 | uint amount0Out, 219 | uint amount1Out, 220 | address indexed to 221 | ); 222 | event Sync(uint112 reserve0, uint112 reserve1); 223 | 224 | function MINIMUM_LIQUIDITY() external pure returns (uint); 225 | 226 | function factory() external view returns (address); 227 | 228 | function token0() external view returns (address); 229 | 230 | function token1() external view returns (address); 231 | 232 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 233 | 234 | function price0CumulativeLast() external view returns (uint); 235 | 236 | function price1CumulativeLast() external view returns (uint); 237 | 238 | function kLast() external view returns (uint); 239 | 240 | function mint(address to) external returns (uint liquidity); 241 | 242 | function burn(address to) external returns (uint amount0, uint amount1); 243 | 244 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 245 | 246 | function skim(address to) external; 247 | 248 | function sync() external; 249 | 250 | function initialize(address, address) external; 251 | } 252 | 253 | interface IERC20 { 254 | event Approval(address indexed owner, address indexed spender, uint value); 255 | event Transfer(address indexed from, address indexed to, uint value); 256 | 257 | function name() external view returns (string memory); 258 | 259 | function symbol() external view returns (string memory); 260 | 261 | function decimals() external view returns (uint8); 262 | 263 | function totalSupply() external view returns (uint); 264 | 265 | function balanceOf(address owner) external view returns (uint); 266 | 267 | function allowance(address owner, address spender) external view returns (uint); 268 | 269 | function approve(address spender, uint value) external returns (bool); 270 | 271 | function transfer(address to, uint value) external returns (bool); 272 | 273 | function transferFrom(address from, address to, uint value) external returns (bool); 274 | } 275 | 276 | interface IWETH { 277 | function deposit() external payable; 278 | 279 | function transfer(address to, uint value) external returns (bool); 280 | 281 | function withdraw(uint) external; 282 | } 283 | -------------------------------------------------------------------------------- /exchanges/pancake/pancake.go: -------------------------------------------------------------------------------- 1 | package pancake 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/ethclient" 8 | "github.com/niccoloCastelli/defiarb/contracts/bep20" 9 | "github.com/niccoloCastelli/defiarb/exchanges" 10 | "github.com/niccoloCastelli/defiarb/exchanges/pancake/contracts/factory" 11 | "github.com/niccoloCastelli/defiarb/exchanges/pancake/contracts/pair" 12 | "github.com/niccoloCastelli/defiarb/exchanges/pancake/contracts/router" 13 | "github.com/niccoloCastelli/defiarb/storage/models" 14 | "github.com/niccoloCastelli/defiarb/utils" 15 | "math" 16 | "math/big" 17 | "sync" 18 | ) 19 | 20 | const ( 21 | RouterAddress = "0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F" 22 | factoryAddress = "0xBCfCcbde45cE874adCB698cC183deBcF17952812" 23 | Name = "pancake" 24 | pairsChanBufferSize = 100 25 | ) 26 | 27 | func init() { 28 | exchanges.RegisterExchange(Name, NewRouter) 29 | } 30 | 31 | func NewRouterWithName(client *ethclient.Client, address string, routerName string) (exchanges.Router, error) { 32 | if address == "" { 33 | address = RouterAddress 34 | } 35 | routerAddr := common.HexToAddress(address) 36 | routerContract, err := router.NewRouter(routerAddr, client) 37 | if err != nil { 38 | return nil, err 39 | } 40 | w, _ := routerContract.WETH(nil) 41 | fmt.Println("router: ", routerAddr.String(), w) 42 | factoryAddr, err := routerContract.Factory(nil) 43 | if err != nil { 44 | return nil, err 45 | } 46 | fmt.Println("factory: ", factoryAddr.String()) 47 | factoryContract, err := factory.NewFactory(factoryAddr, client) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return &Router{ 53 | name: routerName, 54 | client: client, 55 | routerAddr: routerAddr, 56 | factoryAddr: factoryAddr, 57 | router: routerContract, 58 | factory: factoryContract, 59 | }, nil 60 | } 61 | func NewRouter(client *ethclient.Client, address string) (exchanges.Router, error) { 62 | if address == "" { 63 | address = RouterAddress 64 | } 65 | return NewRouterWithName(client, address, Name) 66 | } 67 | 68 | type Router struct { 69 | name string 70 | client *ethclient.Client 71 | routerAddr common.Address 72 | factoryAddr common.Address 73 | router *router.Router 74 | factory *factory.Factory 75 | } 76 | 77 | func (r Router) Name() string { 78 | return r.name 79 | } 80 | func (r Router) GetRouterAddress() common.Address { 81 | return r.routerAddr 82 | } 83 | func (r Router) GetFactoryAddress() common.Address { 84 | return r.factoryAddr 85 | } 86 | func (r Router) GetRouterContract() exchanges.RouterContract { 87 | return r.router 88 | } 89 | func (r Router) GetAllPairsAsync(ctx context.Context) (int64, chan *common.Address, chan error) { 90 | pairAddressesChan := make(chan *common.Address, pairsChanBufferSize) 91 | errChan := make(chan error, 1) 92 | ctx, cancelFn := context.WithCancel(ctx) 93 | 94 | lengthB, err := r.factory.AllPairsLength(nil) 95 | if err != nil { 96 | errChan <- err 97 | pairAddressesChan <- nil 98 | cancelFn() 99 | return 0, pairAddressesChan, errChan 100 | } 101 | length := lengthB.Int64() 102 | 103 | go func() { 104 | defer func() { 105 | cancelFn() 106 | pairAddressesChan <- nil 107 | }() 108 | for i := int64(0); i < length; i++ { 109 | select { 110 | case <-ctx.Done(): 111 | return 112 | default: 113 | pairAddr, err := r.factory.AllPairs(nil, big.NewInt(i)) 114 | if err != nil { 115 | errChan <- err 116 | return 117 | } 118 | pairAddressesChan <- &pairAddr 119 | } 120 | } 121 | }() 122 | 123 | return length, pairAddressesChan, errChan 124 | } 125 | func (r Router) GetAllPairs(ctx context.Context) ([]common.Address, error) { 126 | ctx, cancelFn := context.WithCancel(ctx) 127 | defer cancelFn() 128 | length, pairAddressesChan, errChan := r.GetAllPairsAsync(ctx) 129 | pairAddresses := make([]common.Address, 0, int(length)) 130 | errs := []error{} 131 | 132 | wg := sync.WaitGroup{} 133 | wg.Add(cap(pairAddressesChan)) 134 | go func() { 135 | i := 0 136 | for { 137 | select { 138 | case err := <-errChan: 139 | errs = append(errs, err) 140 | i++ 141 | wg.Done() 142 | case pairAddr := <-pairAddressesChan: 143 | if pairAddr == nil { 144 | cancelFn() 145 | return 146 | } 147 | pairAddresses = append(pairAddresses, *pairAddr) 148 | i++ 149 | wg.Done() 150 | if i%100 == 0 { 151 | fmt.Printf("%d/%d\n", i, length) 152 | } 153 | case <-ctx.Done(): 154 | return 155 | } 156 | } 157 | }() 158 | <-ctx.Done() 159 | return pairAddresses, utils.WrapErrors(errs...) 160 | } 161 | func (r Router) GetPairInfo(address common.Address) (*models.LiquidityPool, error) { 162 | lp, err := pair.NewPair(address, r.client) 163 | if err != nil { 164 | return nil, err 165 | } 166 | name, err := lp.Name(nil) 167 | if err != nil { 168 | return nil, err 169 | } 170 | symbol, err := lp.Symbol(nil) 171 | if err != nil { 172 | return nil, err 173 | } 174 | token0Addr, err := lp.Token0(nil) 175 | if err != nil { 176 | return nil, err 177 | } 178 | token1Addr, err := lp.Token1(nil) 179 | if err != nil { 180 | return nil, err 181 | } 182 | reserves, err := lp.GetReserves(nil) 183 | if err != nil { 184 | return nil, err 185 | } 186 | _, err = lp.Decimals(nil) 187 | if err != nil { 188 | return nil, err 189 | } 190 | token0, err := bep20.NewBep20(token0Addr, r.client) 191 | if err != nil { 192 | return nil, err 193 | } 194 | token1, err := bep20.NewBep20(token1Addr, r.client) 195 | if err != nil { 196 | return nil, err 197 | } 198 | token0Symbol, err := token0.Symbol(nil) 199 | if err != nil { 200 | return nil, err 201 | } 202 | token1Symbol, err := token1.Symbol(nil) 203 | if err != nil { 204 | return nil, err 205 | } 206 | 207 | dbLp := models.LiquidityPool{ 208 | Address: address.String(), 209 | Name: name, 210 | Symbol: symbol, 211 | Description: fmt.Sprintf("%s - %s", token0Symbol, token1Symbol), 212 | Exchange: r.Name(), 213 | Token0Address: token0Addr.String(), 214 | Token1Address: token1Addr.String(), 215 | Token0Weight: 1, 216 | Token1Weight: 1, 217 | Reserve0: utils.NewBigInt(reserves.Reserve0), 218 | Reserve1: utils.NewBigInt(reserves.Reserve1), 219 | } 220 | 221 | return &dbLp, nil 222 | } 223 | func (r Router) GetPairPrice(address common.Address, token0Amount int64, token1Amount int64) (*exchanges.TradeInfo, error) { 224 | lp, err := pair.NewPair(address, r.client) 225 | if err != nil { 226 | return nil, err 227 | } 228 | 229 | token0Addr, err := lp.Token0(nil) 230 | if err != nil { 231 | return nil, err 232 | } 233 | token1Addr, err := lp.Token1(nil) 234 | if err != nil { 235 | return nil, err 236 | } 237 | token0, err := bep20.NewBep20(token0Addr, r.client) 238 | if err != nil { 239 | return nil, err 240 | } 241 | token1, err := bep20.NewBep20(token1Addr, r.client) 242 | if err != nil { 243 | return nil, err 244 | } 245 | token0Decimals, err := token0.Decimals(nil) 246 | if err != nil { 247 | return nil, err 248 | } 249 | token1Decimals, err := token1.Decimals(nil) 250 | if err != nil { 251 | return nil, err 252 | } 253 | 254 | token0Unit := new(big.Int).Mul(big.NewInt(token0Amount), big.NewInt(int64(math.Pow10(int(token0Decimals))))) 255 | token1Unit := new(big.Int).Mul(big.NewInt(token1Amount), big.NewInt(int64(math.Pow10(int(token1Decimals))))) 256 | 257 | /* 258 | reserves, err := lp.GetReserves(nil) 259 | if err != nil { 260 | return 0, err 261 | } 262 | // This is the tricky bit. 263 | // The reserve call returns the reserves for token0 and token1 in a sorted order. 264 | // This means we need to check if our token addresses are sorted or not and flip the reserves if they are not sorted. 265 | stoken0, _ := sortAddressess(token0Addr, token1Addr) 266 | if stoken0 != token0Addr { 267 | // We're not sorted, so the reserves need to be flipped to represent the actual reserves. 268 | reserves.Reserve0, reserves.Reserve1 = reserves.Reserve1, reserves.Reserve0 269 | } 270 | 271 | quote, err := r.router.Quote(nil, token0Unit, reserves.Reserve0, reserves.Reserve1) 272 | if err != nil { 273 | return 0, err 274 | } 275 | return utils.FormatAmount(quote, token1Decimals), nil*/ 276 | 277 | amounts0Out, err := r.router.GetAmountsOut(nil, token0Unit, []common.Address{token0Addr, token1Addr}) 278 | if err != nil { 279 | return nil, err 280 | } 281 | amounts1Out, err := r.router.GetAmountsOut(nil, token1Unit, []common.Address{token1Addr, token0Addr}) 282 | if err != nil { 283 | return nil, err 284 | } 285 | new(big.Int).Quo(amounts0Out[1], token0Unit) 286 | return &exchanges.TradeInfo{ 287 | Token0Price: utils.FormatAmount(new(big.Int).Quo(amounts0Out[1], big.NewInt(token0Amount)), token1Decimals), 288 | Token1Price: utils.FormatAmount(new(big.Int).Quo(amounts1Out[1], big.NewInt(token1Amount)), token0Decimals), 289 | }, nil 290 | } 291 | func (r Router) GetReserves(address common.Address) (float64, float64, error) { 292 | lp, err := pair.NewPair(address, r.client) 293 | if err != nil { 294 | return 0, 0, err 295 | } 296 | token0Addr, err := lp.Token0(nil) 297 | if err != nil { 298 | return 0, 0, err 299 | } 300 | token1Addr, err := lp.Token1(nil) 301 | if err != nil { 302 | return 0, 0, err 303 | } 304 | token0, err := bep20.NewBep20(token0Addr, r.client) 305 | if err != nil { 306 | return 0, 0, err 307 | } 308 | token1, err := bep20.NewBep20(token1Addr, r.client) 309 | if err != nil { 310 | return 0, 0, err 311 | } 312 | token0Decimals, err := token0.Decimals(nil) 313 | if err != nil { 314 | return 0, 0, err 315 | } 316 | token1Decimals, err := token1.Decimals(nil) 317 | if err != nil { 318 | return 0, 0, err 319 | } 320 | reserves, err := lp.GetReserves(nil) 321 | if err != nil { 322 | return 0, 0, err 323 | } 324 | return utils.FormatAmount(reserves.Reserve0, token0Decimals), utils.FormatAmount(reserves.Reserve1, token1Decimals), nil 325 | } 326 | func (r Router) Client() *ethclient.Client { 327 | return r.client 328 | } 329 | 330 | /*lastBlock, err := r.client.BlockByNumber(context.Background(), nil) 331 | if err != nil { 332 | return 0, err 333 | } 334 | optsCurrentBlock := &bind.CallOpts{BlockNumber: lastBlock.Number()} 335 | optsPreviousBlock := &bind.CallOpts{BlockNumber: new(big.Int).Sub(lastBlock.Number(), big.NewInt(1))} 336 | cum0PreviousBlock, err := lp.Price0CumulativeLast(optsPreviousBlock) 337 | if err != nil { 338 | return 0, err 339 | } 340 | cum1PreviousBlock, err := lp.Price1CumulativeLast(optsPreviousBlock) 341 | if err != nil { 342 | return 0, err 343 | } 344 | cum0CurrentBlock, err := lp.Price0CumulativeLast(optsCurrentBlock) 345 | if err != nil { 346 | return 0, err 347 | } 348 | cum1CurrentBlock, err := lp.Price1CumulativeLast(optsCurrentBlock) 349 | if err != nil { 350 | return 0, err 351 | } 352 | price0 := new(big.Int).Set(new(big.Int).Sub(cum0CurrentBlock, cum0PreviousBlock)) 353 | price1 := new(big.Int).Set(new(big.Int).Sub(cum1CurrentBlock, cum1PreviousBlock)) 354 | 355 | fmt.Printf("%d (%d)\n", price0.Uint64(), price1.Uint64())*/ 356 | 357 | func sortAddressess(tkn0, tkn1 common.Address) (common.Address, common.Address) { 358 | token0Rep := big.NewInt(0).SetBytes(tkn0.Bytes()) 359 | token1Rep := big.NewInt(0).SetBytes(tkn1.Bytes()) 360 | 361 | if token0Rep.Cmp(token1Rep) > 0 { 362 | tkn0, tkn1 = tkn1, tkn0 363 | } 364 | 365 | return tkn0, tkn1 366 | } 367 | -------------------------------------------------------------------------------- /exchanges/pancake/pancake_test.go: -------------------------------------------------------------------------------- 1 | package pancake 2 | 3 | import ( 4 | "context" 5 | "github.com/ethereum/go-ethereum/common" 6 | "github.com/ethereum/go-ethereum/ethclient" 7 | "github.com/niccoloCastelli/defiarb/exchanges/pancake/contracts/factory" 8 | "github.com/niccoloCastelli/defiarb/exchanges/pancake/contracts/router" 9 | "github.com/niccoloCastelli/defiarb/services/bsc_client" 10 | "github.com/niccoloCastelli/defiarb/storage/models" 11 | "reflect" 12 | "testing" 13 | ) 14 | 15 | func newClient(t *testing.T) *ethclient.Client { 16 | ctx := context.Background() 17 | client, err := bsc_client.NewClient("ws://192.168.3.3/ws", ctx) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | return client 22 | } 23 | 24 | func TestNewRouter(t *testing.T) { 25 | type args struct { 26 | client *ethclient.Client 27 | address string 28 | } 29 | type testData struct { 30 | name string 31 | args args 32 | want *Router 33 | wantErr bool 34 | } 35 | 36 | client := newClient(t) 37 | 38 | tests := []testData{ 39 | { 40 | args: args{ 41 | client: client, 42 | }, 43 | }, 44 | } 45 | for _, tt := range tests { 46 | t.Run(tt.name, func(t *testing.T) { 47 | got, err := NewRouter(tt.args.client, tt.args.address) 48 | if (err != nil) != tt.wantErr { 49 | t.Errorf("NewRouter() error = %v, wantErr %v", err, tt.wantErr) 50 | return 51 | } 52 | if !reflect.DeepEqual(got, tt.want) { 53 | t.Errorf("NewRouter() got = %v, want %v", got, tt.want) 54 | } 55 | }) 56 | } 57 | } 58 | 59 | func TestRouter_GetAllPairs(t *testing.T) { 60 | type fields struct { 61 | client *ethclient.Client 62 | routerAddr common.Address 63 | factoryAddr common.Address 64 | router *router.Router 65 | factory *factory.Factory 66 | } 67 | client := newClient(t) 68 | 69 | tests := []struct { 70 | name string 71 | fields fields 72 | want []common.Address 73 | wantErr bool 74 | }{ 75 | { 76 | fields: fields{ 77 | client: client, 78 | routerAddr: common.HexToAddress(RouterAddress), 79 | factoryAddr: common.HexToAddress(factoryAddress), 80 | router: nil, 81 | factory: nil, 82 | }, 83 | }, 84 | } 85 | for _, tt := range tests { 86 | t.Run(tt.name, func(t *testing.T) { 87 | r, err := NewRouter(tt.fields.client, tt.fields.factoryAddr.String()) 88 | if err != nil { 89 | t.Errorf("NewRouter error = %v", err) 90 | } 91 | got, err := r.GetAllPairs() 92 | if (err != nil) != tt.wantErr { 93 | t.Errorf("GetAllPairs() error = %v, wantErr %v", err, tt.wantErr) 94 | return 95 | } 96 | if len(got) < 10000 { 97 | t.Errorf("GetAllPairs() got = %v, want %v", got, tt.want) 98 | } 99 | }) 100 | } 101 | } 102 | 103 | func TestRouter_GetPairInfo(t *testing.T) { 104 | type fields struct { 105 | client *ethclient.Client 106 | routerAddr common.Address 107 | factoryAddr common.Address 108 | router *router.Router 109 | factory *factory.Factory 110 | } 111 | type args struct { 112 | address common.Address 113 | } 114 | client := newClient(t) 115 | router, err := NewRouter(client, RouterAddress) 116 | if err != nil { 117 | t.Error(err) 118 | } 119 | tests := []struct { 120 | name string 121 | fields *Router 122 | args args 123 | want *models.LiquidityPool 124 | wantErr bool 125 | }{ 126 | { 127 | fields: router.(*Router), 128 | args: args{ 129 | address: common.HexToAddress("0xA527a61703D82139F8a06Bc30097cC9CAA2df5A6"), 130 | }, 131 | want: &models.LiquidityPool{ 132 | Address: "0xA527a61703D82139F8a06Bc30097cC9CAA2df5A6", 133 | Name: "Pancake LPs", 134 | Symbol: "Cake-LP", 135 | Exchange: "0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F", 136 | Token0Address: "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82", 137 | Token1Address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", 138 | Token0Weight: 1, 139 | Token1Weight: 1, 140 | }, 141 | }, 142 | } 143 | for _, tt := range tests { 144 | t.Run(tt.name, func(t *testing.T) { 145 | r := Router{ 146 | client: tt.fields.client, 147 | routerAddr: tt.fields.routerAddr, 148 | factoryAddr: tt.fields.factoryAddr, 149 | router: tt.fields.router, 150 | factory: tt.fields.factory, 151 | } 152 | got, err := r.GetPairInfo(tt.args.address) 153 | if (err != nil) != tt.wantErr { 154 | t.Errorf("GetPairInfo() error = %v, wantErr %v", err, tt.wantErr) 155 | return 156 | } 157 | if !reflect.DeepEqual(got, tt.want) { 158 | t.Errorf("GetPairInfo() got = %v, want %v", got, tt.want) 159 | } 160 | }) 161 | } 162 | } 163 | 164 | func TestRouter_GetPairPrice(t *testing.T) { 165 | type fields struct { 166 | client *ethclient.Client 167 | routerAddr common.Address 168 | factoryAddr common.Address 169 | router *router.Router 170 | factory *factory.Factory 171 | } 172 | client := newClient(t) 173 | router, err := NewRouter(client, RouterAddress) 174 | if err != nil { 175 | t.Error(err) 176 | } 177 | type args struct { 178 | address common.Address 179 | } 180 | tests := []struct { 181 | name string 182 | fields *Router 183 | args args 184 | want float64 185 | wantErr bool 186 | }{ 187 | { 188 | fields: router.(*Router), 189 | args: args{ 190 | address: common.HexToAddress("0xA527a61703D82139F8a06Bc30097cC9CAA2df5A6"), 191 | }, 192 | }, 193 | } 194 | for _, tt := range tests { 195 | t.Run(tt.name, func(t *testing.T) { 196 | r := Router{ 197 | client: tt.fields.client, 198 | routerAddr: tt.fields.routerAddr, 199 | factoryAddr: tt.fields.factoryAddr, 200 | router: tt.fields.router, 201 | factory: tt.fields.factory, 202 | } 203 | got, err := r.GetPairPrice(tt.args.address, 0, 0) 204 | if (err != nil) != tt.wantErr { 205 | t.Errorf("GetPairPrice() error = %v, wantErr %v", err, tt.wantErr) 206 | return 207 | } 208 | if got == nil { 209 | t.Errorf("GetPairPrice() got = %v", got) 210 | } 211 | }) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /exchanges/pandayield/pandayield.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/ethclient" 5 | "github.com/niccoloCastelli/defiarb/exchanges" 6 | "github.com/niccoloCastelli/defiarb/exchanges/pancake" 7 | ) 8 | 9 | const ( 10 | routerAddress = "--" 11 | name = "pandayield" 12 | ) 13 | 14 | func init() { 15 | exchanges.RegisterExchange(name, NewRouter) 16 | } 17 | 18 | func NewRouter(client *ethclient.Client, address string) (exchanges.Router, error) { 19 | return pancake.NewRouter(client, routerAddress) 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/niccoloCastelli/defiarb 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.0 // indirect 7 | github.com/cespare/cp v1.1.1 // indirect 8 | github.com/deckarep/golang-set v1.7.1 // indirect 9 | github.com/ethereum/go-ethereum v1.10.6 10 | github.com/go-kit/kit v0.9.0 // indirect 11 | github.com/jinzhu/gorm v1.9.16 12 | github.com/kevinburke/ssh_config v1.1.0 // indirect 13 | github.com/kr/pretty v0.3.0 // indirect 14 | github.com/mattn/go-colorable v0.1.2 // indirect 15 | github.com/pkg/errors v0.9.1 16 | github.com/prometheus/tsdb v0.10.0 // indirect 17 | github.com/rjeczalik/notify v0.9.2 // indirect 18 | github.com/rogpeppe/go-internal v1.8.0 // indirect 19 | github.com/sergi/go-diff v1.2.0 // indirect 20 | github.com/spf13/cobra v1.1.1 21 | github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48 // indirect 22 | github.com/tyler-smith/go-bip39 v1.0.2 // indirect 23 | github.com/xanzy/ssh-agent v0.3.0 // indirect 24 | go.uber.org/atomic v1.4.0 25 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect 26 | golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect 27 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 28 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 29 | gopkg.in/src-d/go-git.v4 v4.13.1 30 | ) 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import "github.com/niccoloCastelli/defiarb/cmd" 19 | 20 | var ( 21 | Version = "v0.0.0" 22 | Revision = "" 23 | Branch = "" 24 | ) 25 | 26 | func init() { 27 | cmd.SetVersion(Version, Revision, Branch) 28 | } 29 | 30 | func main() { 31 | cmd.Execute() 32 | } 33 | -------------------------------------------------------------------------------- /services/bsc_client/bsc_client.go: -------------------------------------------------------------------------------- 1 | package bsc_client 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/ethereum/go-ethereum/ethclient" 7 | ) 8 | 9 | func NewClient(rawUrl string, ctx context.Context) (*ethclient.Client, error) { 10 | if rawUrl == "" { 11 | rawUrl = "https://bsc-dataseed.binance.org/" 12 | } 13 | client, err := ethclient.Dial(rawUrl) 14 | if err != nil { 15 | return nil, err 16 | } 17 | chainId, err := client.ChainID(ctx) 18 | if err != nil { 19 | return nil, err 20 | } 21 | if chainId.Uint64() != 56 { 22 | return nil, errors.New("wrong chain id") 23 | } 24 | return client, nil 25 | } 26 | -------------------------------------------------------------------------------- /services/runners/arbitrage.go: -------------------------------------------------------------------------------- 1 | package runners 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/crypto" 9 | "github.com/ethereum/go-ethereum/ethclient" 10 | "github.com/niccoloCastelli/defiarb/services/runners/contracts/arbitrage" 11 | "go.uber.org/atomic" 12 | "math/big" 13 | "os" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | const ( 19 | sleepTime = time.Second * 15 20 | ) 21 | 22 | type ArbitrageRoute struct { 23 | Router1 common.Address 24 | Router2 common.Address 25 | Token0 common.Address 26 | Token1 common.Address 27 | Token2 common.Address 28 | Amount0 *big.Int 29 | Amount1 *big.Int 30 | Amount2 *big.Int 31 | Key string 32 | } 33 | 34 | type ArbitrageRunner struct { 35 | client *ethclient.Client 36 | contractAddress common.Address 37 | isBusy atomic.Bool 38 | } 39 | 40 | func NewArbitrageRunner(client *ethclient.Client, contractAddress common.Address) *ArbitrageRunner { 41 | return &ArbitrageRunner{client: client, contractAddress: contractAddress} 42 | } 43 | 44 | func (r *ArbitrageRunner) IsBusy() bool { 45 | return r.isBusy.Load() 46 | } 47 | func (r *ArbitrageRunner) callArbitrage(ctx context.Context, caller *arbitrage.Arbitrage, args ArbitrageRoute) error { 48 | ctx, cancelFn := context.WithTimeout(ctx, time.Second*5) 49 | defer cancelFn() 50 | privKey, err := crypto.LoadECDSA(os.Getenv("KEY_FILE")) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | auth, err := bind.NewKeyedTransactorWithChainID(privKey, big.NewInt(56)) 56 | if err != nil { 57 | return err 58 | } 59 | txn, err := caller.StartArbitrage(auth, args.Router1, args.Router2, args.Token0, args.Token1, args.Token2, args.Amount0, args.Amount1, args.Amount2) 60 | if err != nil { 61 | if strings.HasPrefix(err.Error(), "failed to estimate gas needed: gas required exceeds allowance") { 62 | auth.GasLimit = 4000000 63 | auth.GasPrice = big.NewInt(5000000000) 64 | txn, err = caller.StartArbitrage(auth, args.Router1, args.Router2, args.Token0, args.Token1, args.Token2, args.Amount0, args.Amount1, args.Amount2) 65 | if err != nil { 66 | fmt.Println("Contract call error: ", err.Error()) 67 | return err 68 | } 69 | } else { 70 | fmt.Println("Contract call error: ", err.Error()) 71 | return err 72 | } 73 | 74 | } 75 | tx, isPending, err := r.client.TransactionByHash(ctx, txn.Hash()) 76 | if err != nil { 77 | return err 78 | } 79 | fmt.Println("contract called, txn hash: ", tx.Hash().String(), isPending, err) 80 | return nil 81 | } 82 | func (r *ArbitrageRunner) Run(ctx context.Context, inputChan chan ArbitrageRoute) error { 83 | caller, err := arbitrage.NewArbitrage(r.contractAddress, r.client) 84 | if err != nil { 85 | return err 86 | } 87 | go func() { 88 | var lastCall time.Time 89 | for { 90 | select { 91 | case args := <-inputChan: 92 | if r.IsBusy() { 93 | continue 94 | } 95 | if time.Now().Sub(lastCall) < time.Minute*5 { 96 | fmt.Println("Skip repetition", args.Key) 97 | } 98 | r.isBusy.Store(true) 99 | fmt.Println(args.Router1, args.Router2, args.Token1, args.Amount1.Uint64()) 100 | if err := r.callArbitrage(ctx, caller, args); err != nil { 101 | fmt.Println("callArbitrage error: ", err.Error()) 102 | lastCall = time.Now() 103 | time.Sleep(sleepTime) 104 | r.isBusy.Store(false) 105 | continue 106 | } 107 | time.Sleep(sleepTime) 108 | lastCall = time.Now() 109 | r.isBusy.Store(false) 110 | // router1 common.Address, router2 common.Address, token0 common.Address, token1 common.Address, token2 common.Address, amount0 *big.Int, amount1 *big.Int, amount2 *big.Int 111 | 112 | case <-ctx.Done(): 113 | return 114 | } 115 | } 116 | }() 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /services/runners/arbitrage_test.go: -------------------------------------------------------------------------------- 1 | package runners 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/ethclient" 8 | "github.com/niccoloCastelli/defiarb/services/runners/contracts/arbitrage" 9 | "github.com/niccoloCastelli/defiarb/utils" 10 | "go.uber.org/atomic" 11 | "math/big" 12 | "testing" 13 | ) 14 | 15 | func newTestClient(ctx context.Context) (*ethclient.Client, error) { 16 | client, err := ethclient.Dial(utils.GetEnv("ETH_TEST_CLIENT", "http://127.0.0.1:8545")) 17 | //client, err := ethclient.Dial(utils.GetEnv("ETH_TEST_CLIENT", "https://bsc-dataseed.binance.org/")) 18 | if err != nil { 19 | return nil, err 20 | } 21 | chainId, err := client.ChainID(ctx) 22 | if err != nil { 23 | return nil, err 24 | } 25 | fmt.Println("chain id: ", chainId) 26 | return client, nil 27 | } 28 | 29 | func TestCallArbitrage(t *testing.T) { 30 | ctx := context.Background() 31 | client, err := newTestClient(ctx) 32 | 33 | //contractAddress := common.HexToAddress("0x4a81cff73f1b8c6d94f50EDC08A4DEe7fbC109C6") //LOCALE 34 | contractAddress := common.HexToAddress("0x98Eb6aD42B9924A14450eaf6BEdC691ebd7E5791") //PROD 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | caller, err := arbitrage.NewArbitrage(contractAddress, client) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | type fields struct { 44 | client *ethclient.Client 45 | contractAddress common.Address 46 | isBusy atomic.Bool 47 | } 48 | type args struct { 49 | ctx context.Context 50 | caller *arbitrage.Arbitrage 51 | args ArbitrageRoute 52 | } 53 | tests := []struct { 54 | name string 55 | fields fields 56 | args args 57 | wantErr bool 58 | }{ 59 | { 60 | fields: fields{ 61 | client: client, 62 | contractAddress: contractAddress, 63 | }, 64 | args: args{ //Flash opportunity! 6 DAI->WBNB->BUSD 1.002517542306942 2021-04-18 12:25:54.910438051 +0200 CEST m=+341.790852059 1000000000000000000 [pancake bakery pancake] [0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56] 65 | ctx: ctx, 66 | caller: caller, 67 | args: ArbitrageRoute{ 68 | Router1: common.HexToAddress("0xcde540d7eafe93ac5fe6233bee57e1270d3e330f"), 69 | Router2: common.HexToAddress("0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F"), 70 | Token0: common.HexToAddress("0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3"), 71 | Token1: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), 72 | Token2: common.HexToAddress("0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"), 73 | Amount0: big.NewInt(0), 74 | Amount1: utils.GetAmount(1, 18), 75 | Amount2: big.NewInt(0), 76 | }, 77 | }, 78 | }, 79 | /*{ 80 | fields: fields{ 81 | client: client, 82 | contractAddress: contractAddress, 83 | }, 84 | args: args{ 85 | ctx: ctx, 86 | caller: caller, 87 | args: ArbitrageRoute{ 88 | Router1: common.HexToAddress("0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F"), 89 | Router2: common.HexToAddress("0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F"), 90 | Token0: common.HexToAddress("0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3"), 91 | Token1: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), 92 | Token2: common.HexToAddress("0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"), 93 | Amount0: big.NewInt(0), 94 | Amount1: utils.GetAmount(1,18), 95 | Amount2: big.NewInt(0), 96 | }, 97 | }, 98 | }, 99 | { 100 | fields: fields{ 101 | client: client, 102 | contractAddress: contractAddress, 103 | }, 104 | args: args{ 105 | ctx: ctx, 106 | caller: caller, 107 | args: ArbitrageRoute{ 108 | Router1: common.HexToAddress("0xcde540d7eafe93ac5fe6233bee57e1270d3e330f"), 109 | Router2: common.HexToAddress("0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F"), 110 | Token0: common.HexToAddress("0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3"), 111 | Token1: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), 112 | Token2: common.HexToAddress("0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"), 113 | Amount0: big.NewInt(0), 114 | Amount1: utils.GetAmount(1,18), 115 | Amount2: big.NewInt(0), 116 | }, 117 | }, 118 | }, 119 | { 120 | fields: fields{ 121 | client: client, 122 | contractAddress: contractAddress, 123 | }, 124 | args: args{ 125 | ctx: ctx, 126 | caller: caller, 127 | args: ArbitrageRoute{ 128 | Router1: common.HexToAddress("0xcde540d7eafe93ac5fe6233bee57e1270d3e330f"), 129 | Router2: common.HexToAddress(""), 130 | Token0: common.HexToAddress("0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"), 131 | Token1: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), 132 | Token2: common.HexToAddress(""), 133 | Amount0: big.NewInt(0), 134 | Amount1: utils.GetAmount(1,18), 135 | Amount2: big.NewInt(0), 136 | }, 137 | }, 138 | }, 139 | { 140 | fields: fields{ 141 | client: client, 142 | contractAddress: contractAddress, 143 | }, 144 | args: args{ 145 | ctx: ctx, 146 | caller: caller, 147 | args: ArbitrageRoute{ 148 | Router1: common.HexToAddress("0xb7e19a1188776f32e8c2b790d9ca578f2896da7c"), 149 | Router2: common.HexToAddress(""), 150 | Token0: common.HexToAddress("0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"), 151 | Token1: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), 152 | Token2: common.HexToAddress(""), 153 | Amount0: big.NewInt(0), 154 | Amount1: utils.GetAmount(1,18), 155 | Amount2: big.NewInt(0), 156 | }, 157 | }, 158 | }, 159 | { //Flash opportunity! 1 WBNB->ETH [pancake value] [0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c 0x2170Ed0880ac9A755fd29B2688956BD959F933F8] 160 | fields: fields{ 161 | client: client, 162 | contractAddress: contractAddress, 163 | }, 164 | args: args{ 165 | ctx: ctx, 166 | caller: caller, 167 | args: ArbitrageRoute{ 168 | Router1: common.HexToAddress("0xb7e19a1188776f32e8c2b790d9ca578f2896da7c"), 169 | Router2: common.HexToAddress(""), 170 | Token0: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), 171 | Token1: common.HexToAddress("0x2170Ed0880ac9A755fd29B2688956BD959F933F8"), 172 | Token2: common.HexToAddress(""), 173 | Amount0: big.NewInt(0), 174 | Amount1: utils.GetAmount(1,18), 175 | Amount2: big.NewInt(0), 176 | }, 177 | }, 178 | }, 179 | { //Flash opportunity! 1 WBNB->ETH [pancake value] [0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c 0x2170Ed0880ac9A755fd29B2688956BD959F933F8] 180 | fields: fields{ 181 | client: client, 182 | contractAddress: contractAddress, 183 | }, 184 | args: args{ 185 | ctx: ctx, 186 | caller: caller, 187 | args: ArbitrageRoute{ 188 | Router1: common.HexToAddress("0xcde540d7eafe93ac5fe6233bee57e1270d3e330f"), 189 | Router2: common.HexToAddress(""), 190 | Token0: common.HexToAddress("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), 191 | Token1: common.HexToAddress("0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"), 192 | Token2: common.HexToAddress(""), 193 | Amount0: big.NewInt(0), 194 | Amount1: utils.GetAmount(1,18), 195 | Amount2: big.NewInt(0), 196 | }, 197 | }, 198 | },*/ 199 | } 200 | for _, tt := range tests { 201 | t.Run(tt.name, func(t *testing.T) { 202 | r := &ArbitrageRunner{ 203 | client: tt.fields.client, 204 | contractAddress: tt.fields.contractAddress, 205 | isBusy: tt.fields.isBusy, 206 | } 207 | if err := r.callArbitrage(tt.args.ctx, tt.args.caller, tt.args.args); (err != nil) != tt.wantErr { 208 | t.Errorf("callArbitrage() error = %v, wantErr %v", err, tt.wantErr) 209 | } 210 | }) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /services/runners/contracts/arbitrage/IArbitrage.go: -------------------------------------------------------------------------------- 1 | // Code generated - DO NOT EDIT. 2 | // This file is a generated binding and any manual changes will be lost. 3 | 4 | package arbitrage 5 | 6 | import ( 7 | "math/big" 8 | "strings" 9 | 10 | ethereum "github.com/ethereum/go-ethereum" 11 | "github.com/ethereum/go-ethereum/accounts/abi" 12 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 13 | "github.com/ethereum/go-ethereum/common" 14 | "github.com/ethereum/go-ethereum/core/types" 15 | "github.com/ethereum/go-ethereum/event" 16 | ) 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var ( 20 | _ = big.NewInt 21 | _ = strings.NewReader 22 | _ = ethereum.NotFound 23 | _ = bind.Bind 24 | _ = common.Big1 25 | _ = types.BloomLookup 26 | _ = event.NewSubscription 27 | ) 28 | 29 | // ArbitrageABI is the input ABI used to generate the binding from. 30 | const ArbitrageABI = "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router1\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"router2\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token2\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount2\",\"type\":\"uint256\"}],\"name\":\"startArbitrage\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}]" 31 | 32 | // Arbitrage is an auto generated Go binding around an Ethereum contract. 33 | type Arbitrage struct { 34 | ArbitrageCaller // Read-only binding to the contract 35 | ArbitrageTransactor // Write-only binding to the contract 36 | ArbitrageFilterer // Log filterer for contract events 37 | } 38 | 39 | // ArbitrageCaller is an auto generated read-only Go binding around an Ethereum contract. 40 | type ArbitrageCaller struct { 41 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 42 | } 43 | 44 | // ArbitrageTransactor is an auto generated write-only Go binding around an Ethereum contract. 45 | type ArbitrageTransactor struct { 46 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 47 | } 48 | 49 | // ArbitrageFilterer is an auto generated log filtering Go binding around an Ethereum contract events. 50 | type ArbitrageFilterer struct { 51 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 52 | } 53 | 54 | // ArbitrageSession is an auto generated Go binding around an Ethereum contract, 55 | // with pre-set call and transact options. 56 | type ArbitrageSession struct { 57 | Contract *Arbitrage // Generic contract binding to set the session for 58 | CallOpts bind.CallOpts // Call options to use throughout this session 59 | TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session 60 | } 61 | 62 | // ArbitrageCallerSession is an auto generated read-only Go binding around an Ethereum contract, 63 | // with pre-set call options. 64 | type ArbitrageCallerSession struct { 65 | Contract *ArbitrageCaller // Generic contract caller binding to set the session for 66 | CallOpts bind.CallOpts // Call options to use throughout this session 67 | } 68 | 69 | // ArbitrageTransactorSession is an auto generated write-only Go binding around an Ethereum contract, 70 | // with pre-set transact options. 71 | type ArbitrageTransactorSession struct { 72 | Contract *ArbitrageTransactor // Generic contract transactor binding to set the session for 73 | TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session 74 | } 75 | 76 | // ArbitrageRaw is an auto generated low-level Go binding around an Ethereum contract. 77 | type ArbitrageRaw struct { 78 | Contract *Arbitrage // Generic contract binding to access the raw methods on 79 | } 80 | 81 | // ArbitrageCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. 82 | type ArbitrageCallerRaw struct { 83 | Contract *ArbitrageCaller // Generic read-only contract binding to access the raw methods on 84 | } 85 | 86 | // ArbitrageTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. 87 | type ArbitrageTransactorRaw struct { 88 | Contract *ArbitrageTransactor // Generic write-only contract binding to access the raw methods on 89 | } 90 | 91 | // NewArbitrage creates a new instance of Arbitrage, bound to a specific deployed contract. 92 | func NewArbitrage(address common.Address, backend bind.ContractBackend) (*Arbitrage, error) { 93 | contract, err := bindArbitrage(address, backend, backend, backend) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return &Arbitrage{ArbitrageCaller: ArbitrageCaller{contract: contract}, ArbitrageTransactor: ArbitrageTransactor{contract: contract}, ArbitrageFilterer: ArbitrageFilterer{contract: contract}}, nil 98 | } 99 | 100 | // NewArbitrageCaller creates a new read-only instance of Arbitrage, bound to a specific deployed contract. 101 | func NewArbitrageCaller(address common.Address, caller bind.ContractCaller) (*ArbitrageCaller, error) { 102 | contract, err := bindArbitrage(address, caller, nil, nil) 103 | if err != nil { 104 | return nil, err 105 | } 106 | return &ArbitrageCaller{contract: contract}, nil 107 | } 108 | 109 | // NewArbitrageTransactor creates a new write-only instance of Arbitrage, bound to a specific deployed contract. 110 | func NewArbitrageTransactor(address common.Address, transactor bind.ContractTransactor) (*ArbitrageTransactor, error) { 111 | contract, err := bindArbitrage(address, nil, transactor, nil) 112 | if err != nil { 113 | return nil, err 114 | } 115 | return &ArbitrageTransactor{contract: contract}, nil 116 | } 117 | 118 | // NewArbitrageFilterer creates a new log filterer instance of Arbitrage, bound to a specific deployed contract. 119 | func NewArbitrageFilterer(address common.Address, filterer bind.ContractFilterer) (*ArbitrageFilterer, error) { 120 | contract, err := bindArbitrage(address, nil, nil, filterer) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return &ArbitrageFilterer{contract: contract}, nil 125 | } 126 | 127 | // bindArbitrage binds a generic wrapper to an already deployed contract. 128 | func bindArbitrage(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { 129 | parsed, err := abi.JSON(strings.NewReader(ArbitrageABI)) 130 | if err != nil { 131 | return nil, err 132 | } 133 | return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil 134 | } 135 | 136 | // Call invokes the (constant) contract method with params as input values and 137 | // sets the output to result. The result type might be a single field for simple 138 | // returns, a slice of interfaces for anonymous returns and a struct for named 139 | // returns. 140 | func (_Arbitrage *ArbitrageRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { 141 | return _Arbitrage.Contract.ArbitrageCaller.contract.Call(opts, result, method, params...) 142 | } 143 | 144 | // Transfer initiates a plain transaction to move funds to the contract, calling 145 | // its default method if one is available. 146 | func (_Arbitrage *ArbitrageRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { 147 | return _Arbitrage.Contract.ArbitrageTransactor.contract.Transfer(opts) 148 | } 149 | 150 | // Transact invokes the (paid) contract method with params as input values. 151 | func (_Arbitrage *ArbitrageRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { 152 | return _Arbitrage.Contract.ArbitrageTransactor.contract.Transact(opts, method, params...) 153 | } 154 | 155 | // Call invokes the (constant) contract method with params as input values and 156 | // sets the output to result. The result type might be a single field for simple 157 | // returns, a slice of interfaces for anonymous returns and a struct for named 158 | // returns. 159 | func (_Arbitrage *ArbitrageCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { 160 | return _Arbitrage.Contract.contract.Call(opts, result, method, params...) 161 | } 162 | 163 | // Transfer initiates a plain transaction to move funds to the contract, calling 164 | // its default method if one is available. 165 | func (_Arbitrage *ArbitrageTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { 166 | return _Arbitrage.Contract.contract.Transfer(opts) 167 | } 168 | 169 | // Transact invokes the (paid) contract method with params as input values. 170 | func (_Arbitrage *ArbitrageTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { 171 | return _Arbitrage.Contract.contract.Transact(opts, method, params...) 172 | } 173 | 174 | // StartArbitrage is a paid mutator transaction binding the contract method 0xbe493263. 175 | // 176 | // Solidity: function startArbitrage(address router1, address router2, address token0, address token1, address token2, uint256 amount0, uint256 amount1, uint256 amount2) payable returns() 177 | func (_Arbitrage *ArbitrageTransactor) StartArbitrage(opts *bind.TransactOpts, router1 common.Address, router2 common.Address, token0 common.Address, token1 common.Address, token2 common.Address, amount0 *big.Int, amount1 *big.Int, amount2 *big.Int) (*types.Transaction, error) { 178 | return _Arbitrage.contract.Transact(opts, "startArbitrage", router1, router2, token0, token1, token2, amount0, amount1, amount2) 179 | } 180 | 181 | // StartArbitrage is a paid mutator transaction binding the contract method 0xbe493263. 182 | // 183 | // Solidity: function startArbitrage(address router1, address router2, address token0, address token1, address token2, uint256 amount0, uint256 amount1, uint256 amount2) payable returns() 184 | func (_Arbitrage *ArbitrageSession) StartArbitrage(router1 common.Address, router2 common.Address, token0 common.Address, token1 common.Address, token2 common.Address, amount0 *big.Int, amount1 *big.Int, amount2 *big.Int) (*types.Transaction, error) { 185 | return _Arbitrage.Contract.StartArbitrage(&_Arbitrage.TransactOpts, router1, router2, token0, token1, token2, amount0, amount1, amount2) 186 | } 187 | 188 | // StartArbitrage is a paid mutator transaction binding the contract method 0xbe493263. 189 | // 190 | // Solidity: function startArbitrage(address router1, address router2, address token0, address token1, address token2, uint256 amount0, uint256 amount1, uint256 amount2) payable returns() 191 | func (_Arbitrage *ArbitrageTransactorSession) StartArbitrage(router1 common.Address, router2 common.Address, token0 common.Address, token1 common.Address, token2 common.Address, amount0 *big.Int, amount1 *big.Int, amount2 *big.Int) (*types.Transaction, error) { 192 | return _Arbitrage.Contract.StartArbitrage(&_Arbitrage.TransactOpts, router1, router2, token0, token1, token2, amount0, amount1, amount2) 193 | } 194 | -------------------------------------------------------------------------------- /services/runners/contracts/build.sh: -------------------------------------------------------------------------------- 1 | 2 | mkdir -p build 3 | mkdir -p arbitrage 4 | solc --overwrite --abi --bin ./src/IArbitrage.sol -o build 5 | abigen --bin=./build/IArbitrage.bin --abi=./build/IArbitrage.abi --pkg=arbitrage --out=arbitrage/IArbitrage.go 6 | -------------------------------------------------------------------------------- /services/runners/contracts/build/IArbitrage.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"router1","type":"address"},{"internalType":"address","name":"router2","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"address","name":"token2","type":"address"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"},{"internalType":"uint256","name":"amount2","type":"uint256"}],"name":"startArbitrage","outputs":[],"stateMutability":"payable","type":"function"}] -------------------------------------------------------------------------------- /services/runners/contracts/build/IArbitrage.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niccoloCastelli/defiarb/dee77e494588c076b47cfc450ec7abef6064c00f/services/runners/contracts/build/IArbitrage.bin -------------------------------------------------------------------------------- /services/runners/contracts/src/IArbitrage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.3; 2 | pragma experimental ABIEncoderV2; 3 | 4 | interface IArbitrage { 5 | function startArbitrage(address router1, 6 | address router2, 7 | address token0, 8 | address token1, 9 | address token2, 10 | uint amount0, 11 | uint amount1, 12 | uint amount2 13 | ) external payable; 14 | } -------------------------------------------------------------------------------- /services/scanners/lp_scanner.go: -------------------------------------------------------------------------------- 1 | package scanners 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum" 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/core/types" 9 | "github.com/ethereum/go-ethereum/ethclient" 10 | "github.com/ethereum/go-ethereum/rpc" 11 | "github.com/jinzhu/gorm" 12 | "github.com/niccoloCastelli/defiarb/contracts/bep20" 13 | "github.com/niccoloCastelli/defiarb/contracts/pancake_pair" 14 | "github.com/niccoloCastelli/defiarb/storage/models" 15 | "github.com/pkg/errors" 16 | "math/big" 17 | ) 18 | 19 | const ( 20 | chunkSize = 10000 21 | addrChanBufferSize = 20000 22 | ) 23 | 24 | type LpScanner struct { 25 | client *ethclient.Client 26 | db *gorm.DB 27 | } 28 | 29 | func NewLpScanner(client *ethclient.Client, db *gorm.DB) *LpScanner { 30 | return &LpScanner{client: client, db: db} 31 | } 32 | 33 | func (s *LpScanner) Scan(ctx context.Context, address common.Address) ([]*pancake_pair.Pair, error) { 34 | lastBlock, err := s.client.BlockByNumber(ctx, nil) 35 | if err != nil { 36 | return nil, errors.WithStack(err) 37 | } 38 | ctx, cancelFn := context.WithCancel(ctx) 39 | defer cancelFn() 40 | ret := []*pancake_pair.Pair{} 41 | lastBlockNumber := lastBlock.Number().Int64() 42 | addressesToScan := make(chan common.Address, addrChanBufferSize) 43 | logsChan := make(chan types.Log, addrChanBufferSize*5) 44 | errChan := make(chan error, 1) 45 | var startBlock int64 = 1 46 | 47 | // Get transaction logs for block chunks 48 | go func() { 49 | defer cancelFn() 50 | for i := lastBlockNumber; i > startBlock; i = i - chunkSize { 51 | select { 52 | case <-ctx.Done(): 53 | return 54 | default: 55 | fromBlock := i - chunkSize 56 | if fromBlock < 0 { 57 | fromBlock = 0 58 | } 59 | fmt.Printf("Blocks: %d-%d \n", fromBlock, i) 60 | logs, err := s.client.FilterLogs(ctx, ethereum.FilterQuery{ 61 | FromBlock: big.NewInt(fromBlock), 62 | ToBlock: big.NewInt(i), 63 | Addresses: []common.Address{address}, // 64 | }) 65 | if err != nil { 66 | errChan <- errors.WithStack(err) 67 | return 68 | } 69 | for _, log := range logs { 70 | logsChan <- log 71 | } 72 | } 73 | } 74 | }() 75 | 76 | // Get transaction receipts for logs 77 | go func() { 78 | for { 79 | select { 80 | case log := <-logsChan: 81 | tx, err := s.client.TransactionReceipt(ctx, log.TxHash) 82 | if err != nil { 83 | if jErr, ok := err.(rpc.Error); ok { 84 | switch jErr.ErrorCode() { 85 | case -32000: 86 | //fmt.Println("JsonRPC error", jErr.ErrorCode(), jErr.Error()) 87 | continue 88 | default: 89 | fmt.Println("JsonRPC error", jErr.ErrorCode(), jErr.Error()) 90 | } 91 | } 92 | errChan <- errors.WithStack(err) 93 | return 94 | } 95 | 96 | for _, txLog := range tx.Logs { 97 | addressesToScan <- txLog.Address 98 | } 99 | case <-ctx.Done(): 100 | return 101 | } 102 | 103 | } 104 | }() 105 | 106 | savedTokens := map[string]common.Address{} 107 | savedLps := map[string]common.Address{} 108 | for { 109 | select { 110 | case err := <-errChan: 111 | return nil, err 112 | case addrToScan := <-addressesToScan: 113 | _, lpSaved := savedLps[addrToScan.String()] 114 | _, tokenSaved := savedTokens[addrToScan.String()] 115 | if lpSaved || tokenSaved { 116 | continue 117 | } 118 | _, saved, err := s.saveLp(addrToScan, address) 119 | if err != nil { 120 | return nil, err 121 | } 122 | if saved { 123 | savedLps[addrToScan.String()] = addrToScan 124 | continue 125 | } 126 | _, saved, err = s.saveToken(addrToScan) 127 | if err != nil { 128 | return nil, err 129 | } 130 | if saved { 131 | savedTokens[addrToScan.String()] = addrToScan 132 | continue 133 | } 134 | continue 135 | case <-ctx.Done(): 136 | goto endloop 137 | } 138 | } 139 | endloop: 140 | return ret, nil 141 | } 142 | func (s LpScanner) saveLp(addr common.Address, exchangeAddr common.Address) (*models.LiquidityPool, bool, error) { 143 | pair, err := pancake_pair.NewPair(addr, s.client) 144 | if err != nil { 145 | return nil, false, nil 146 | } 147 | name, err := pair.Name(nil) 148 | if err != nil { 149 | return nil, false, nil 150 | } 151 | symbol, err := pair.Symbol(nil) 152 | if err != nil { 153 | return nil, false, nil 154 | } 155 | token0Addr, err := pair.Token0(nil) 156 | if err != nil { 157 | return nil, false, nil 158 | } 159 | token1Addr, err := pair.Token1(nil) 160 | if err != nil { 161 | return nil, false, nil 162 | } 163 | 164 | dbLp := models.LiquidityPool{ 165 | Address: addr.String(), 166 | Name: name, 167 | Symbol: symbol, 168 | Exchange: exchangeAddr.String(), 169 | Token0Address: token0Addr.String(), 170 | Token1Address: token1Addr.String(), 171 | } 172 | 173 | if factoryAddr, err := pair.Factory(nil); err == nil { 174 | dbLp.FactoryAddress = factoryAddr.String() 175 | } 176 | if err := s.db.Save(&dbLp).Error; err != nil { 177 | return nil, false, err 178 | } 179 | //fmt.Printf("AddLpTokenInfo(\"valueDefi\", \"%s\", \"%s\", \"%s\")\n", addr.String(), name, symbol) 180 | // fmt.Printf("[%s] %s %f %f\n", symbol, name, utils.FormatAmount(reserves.Reserve0), utils.FormatAmount(reserves.Reserve1)) 181 | return &dbLp, true, nil 182 | } 183 | func (s LpScanner) saveToken(addr common.Address) (*models.Token, bool, error) { 184 | token, err := bep20.NewBep20(addr, s.client) 185 | if err != nil { 186 | return nil, false, nil 187 | } 188 | name, err := token.Name(nil) 189 | if err != nil { 190 | return nil, false, nil 191 | } 192 | symbol, err := token.Symbol(nil) 193 | if err != nil { 194 | return nil, false, nil 195 | } 196 | decimals, err := token.Decimals(nil) 197 | if err != nil { 198 | return nil, false, nil 199 | } 200 | totalSupply, err := token.TotalSupply(nil) 201 | if err != nil { 202 | return nil, false, err 203 | } 204 | 205 | dbToken := models.Token{ 206 | Address: addr.String(), 207 | Name: name, 208 | Symbol: symbol, 209 | LogoUrl: "", 210 | Decimals: int(decimals), 211 | TotalSupply: totalSupply.Int64(), 212 | } 213 | if err := s.db.Save(&dbToken).Error; err != nil { 214 | return nil, false, err 215 | } 216 | //fmt.Printf("AddLpTokenInfo(\"valueDefi\", \"%s\", \"%s\", \"%s\")\n", addr.String(), name, symbol) 217 | fmt.Printf("[%s] %s %f\n", symbol, name, dbToken.FormattedTotalSupply()) 218 | return &dbToken, true, nil 219 | } 220 | -------------------------------------------------------------------------------- /services/wallet/wallet.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "context" 5 | "github.com/ethereum/go-ethereum" 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/core/types" 8 | "github.com/ethereum/go-ethereum/ethclient" 9 | "math/big" 10 | ) 11 | 12 | type Wallet struct { 13 | address common.Address 14 | client *ethclient.Client 15 | } 16 | 17 | func NewWallet(address common.Address, client *ethclient.Client) *Wallet { 18 | return &Wallet{address: address, client: client} 19 | } 20 | 21 | func (w *Wallet) Address() common.Address { 22 | return w.address 23 | } 24 | func (w *Wallet) GetBalance(ctx context.Context) (uint64, error) { 25 | lastBlock, err := w.client.BlockByNumber(ctx, nil) 26 | if err != nil { 27 | return 0, err 28 | } 29 | 30 | balance, err := w.client.BalanceAt(ctx, w.address, lastBlock.Number()) 31 | if err != nil { 32 | return 0, err 33 | } 34 | return balance.Uint64(), nil 35 | } 36 | func (w *Wallet) GetTransactions(ctx context.Context) ([]types.Log, error) { 37 | lastBlock, err := w.client.BlockByNumber(ctx, nil) 38 | if err != nil { 39 | return nil, err 40 | } 41 | logs, err := w.client.FilterLogs(ctx, ethereum.FilterQuery{ 42 | BlockHash: nil, 43 | FromBlock: big.NewInt(0).Sub(lastBlock.Number(), big.NewInt(4000)), 44 | ToBlock: nil, 45 | Addresses: []common.Address{w.Address()}, 46 | Topics: nil, 47 | }) 48 | return logs, err 49 | } 50 | -------------------------------------------------------------------------------- /storage/db.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | _ "github.com/jinzhu/gorm/dialects/postgres" 6 | "github.com/niccoloCastelli/defiarb/config" 7 | "github.com/niccoloCastelli/defiarb/storage/models" 8 | ) 9 | 10 | func NewDb(conf *config.DbConfig) (*gorm.DB, error) { 11 | return gorm.Open("postgres", conf.ConnectionString()) 12 | } 13 | 14 | func Migrate(db *gorm.DB) error { 15 | tables := []interface{}{ 16 | models.Token{}, 17 | models.LiquidityPool{}, 18 | models.Transaction{}, 19 | } 20 | return db.AutoMigrate(tables...).Error 21 | } 22 | -------------------------------------------------------------------------------- /storage/models/base.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type BaseModel struct { 6 | CreatedAt time.Time 7 | UpdatedAt time.Time 8 | DeletedAt *time.Time 9 | } 10 | -------------------------------------------------------------------------------- /storage/models/liquidity_pool.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "github.com/niccoloCastelli/defiarb/tokens" 6 | "github.com/niccoloCastelli/defiarb/utils" 7 | ) 8 | 9 | type LiquidityPool struct { 10 | BaseModel 11 | Address string `gorm:"primary_key"` 12 | FactoryAddress string 13 | Name string 14 | Symbol string 15 | Description string 16 | Exchange string 17 | Token0Address string 18 | Token1Address string 19 | Token0Weight int 20 | Token1Weight int 21 | Token0 Token 22 | Token1 Token 23 | Reserve0 *utils.BigInt `gorm:"type:bytea"` 24 | Reserve1 *utils.BigInt `gorm:"type:bytea"` 25 | Token0Price float64 `gorm:"-"` 26 | Token1Price float64 `gorm:"-"` 27 | } 28 | 29 | func (p *LiquidityPool) GetTokens(db *gorm.DB) error { 30 | if err := db.Where("lower(address) = lower(?)", p.Token0Address).First(&p.Token0).Error; err != nil { 31 | return err 32 | } 33 | if err := db.Where("lower(address) = lower(?)", p.Token1Address).First(&p.Token1).Error; err != nil { 34 | return err 35 | } 36 | return nil 37 | } 38 | 39 | const supportedPoolsQuery = `SELECT liquidity_pools.*, t0.symbol, t1.symbol FROM liquidity_pools 40 | INNER JOIN tokens t0 on liquidity_pools.token0_address = t0.address 41 | INNER JOIN tokens t1 on liquidity_pools.token1_address = t1.address` 42 | 43 | type LiquidityPools []LiquidityPool 44 | 45 | func (l *LiquidityPools) LoadSupportedPools(db *gorm.DB) error { 46 | return db.Raw(supportedPoolsQuery).Scan(&l).Error 47 | } 48 | func (l LiquidityPool) LpToken() *tokens.LpToken { 49 | return tokens.NewLpTokenInfo(l.Exchange, l.Address, l.Name, l.Symbol, l.Token0Address, l.Token1Address) 50 | } 51 | -------------------------------------------------------------------------------- /storage/models/token.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "github.com/niccoloCastelli/defiarb/tokens" 6 | "math" 7 | ) 8 | 9 | type Token struct { 10 | BaseModel 11 | Address string `gorm:"primary_key"` 12 | Name string 13 | Symbol string 14 | LogoUrl string 15 | Decimals int 16 | TotalSupply int64 17 | LoanAmount int64 //`gorm:"-"` 18 | } 19 | 20 | func NewTokenFromERC20(t tokens.Erc20) *Token { 21 | return &Token{ 22 | Address: t.Address(), 23 | Name: t.Name(), 24 | Symbol: t.Code(), 25 | LogoUrl: t.LogoUrl(), 26 | Decimals: 0, 27 | TotalSupply: 0, 28 | LoanAmount: 0, 29 | } 30 | } 31 | func (t Token) FormattedTotalSupply() float64 { 32 | return float64(t.TotalSupply) / math.Pow(10, float64(t.Decimals)) 33 | } 34 | 35 | type Tokens []Token 36 | 37 | func (t *Tokens) GetAll(db *gorm.DB) error { 38 | return db.Find(t).Error 39 | } 40 | -------------------------------------------------------------------------------- /storage/models/transaction.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Transaction struct { 4 | BaseModel 5 | } 6 | -------------------------------------------------------------------------------- /storage/price_graph/graph.go: -------------------------------------------------------------------------------- 1 | package price_graph 2 | 3 | import ( 4 | "fmt" 5 | "github.com/pkg/errors" 6 | ) 7 | 8 | func NewNode(address string, name string) *Node { 9 | return &Node{Address: address, Name: name} 10 | } 11 | 12 | type Node struct { 13 | Address string 14 | Name string 15 | edgesFrom []*Edge 16 | edgesTo []*Edge 17 | } 18 | 19 | type Edge struct { 20 | Address string 21 | Name string 22 | Exchange string 23 | From *Node 24 | To *Node 25 | Weight float64 26 | } 27 | 28 | func NewGraph() *Graph { 29 | return &Graph{ 30 | nodes: map[string]*Node{}, 31 | edges: map[string]*Edge{}, 32 | } 33 | } 34 | 35 | type Graph struct { 36 | nodes map[string]*Node 37 | edges map[string]*Edge 38 | } 39 | 40 | func (g *Graph) SetWeight(edgeKey string, weight float64) error { 41 | edge, ok := g.edges[edgeKey] 42 | if !ok { 43 | return errors.New("edge not found") 44 | } 45 | edge.Weight = weight 46 | return nil 47 | } 48 | 49 | func (g *Graph) AddNode(node Node) { 50 | if _, ok := g.nodes[node.Address]; !ok { 51 | node.edgesFrom = []*Edge{} 52 | node.edgesTo = []*Edge{} 53 | g.nodes[node.Address] = &node 54 | } 55 | } 56 | 57 | func (g *Graph) GetNode(address string) (*Node, error) { 58 | if n, ok := g.nodes[address]; ok { 59 | return n, nil 60 | } 61 | return nil, errors.New("node not found") 62 | } 63 | 64 | func (g *Graph) AddEdge(fromAddr string, toAddr string, weight float64, address string, name string, exchange string) error { 65 | nodeFrom, ok := g.nodes[fromAddr] 66 | if !ok { 67 | return errors.New("node from not found") 68 | } 69 | nodeTo, ok := g.nodes[toAddr] 70 | if !ok { 71 | return errors.New("node to not found") 72 | } 73 | edge := &Edge{ 74 | Address: address, 75 | Name: name, 76 | From: nodeFrom, 77 | To: nodeTo, 78 | Weight: weight, 79 | Exchange: exchange, 80 | } 81 | edgeKey := fmt.Sprintf("%s@%s->%s", address, fromAddr, toAddr) 82 | g.edges[edgeKey] = edge 83 | nodeFrom.edgesFrom = append(nodeFrom.edgesFrom, edge) 84 | nodeTo.edgesTo = append(nodeFrom.edgesTo, edge) 85 | return nil 86 | } 87 | 88 | func EdgeKey(address string, fromAddr string, toAddr string) string { 89 | return fmt.Sprintf("%s@%s->%s", address, fromAddr, toAddr) 90 | } 91 | -------------------------------------------------------------------------------- /storage/price_graph/main.go: -------------------------------------------------------------------------------- 1 | package price_graph 2 | 3 | import ( 4 | "github.com/niccoloCastelli/defiarb/storage/models" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | directTreshold = 1.014 11 | triangularTreshold = 1.015 12 | ) 13 | 14 | func NewPriceGraph(pools ...models.LiquidityPool) *PriceGraph { 15 | poolsMap := map[string]models.LiquidityPool{} 16 | tokensMap := map[string]models.Token{} 17 | for i, pool := range pools { 18 | if _, ok := tokensMap[pool.Token0Address]; !ok { 19 | tokensMap[pool.Token0Address] = pool.Token0 20 | } 21 | if _, ok := tokensMap[pool.Token1Address]; !ok { 22 | tokensMap[pool.Token1Address] = pool.Token1 23 | } 24 | poolsMap[pool.Address] = pools[i] 25 | } 26 | //gr := graphs.GetGraph() 27 | return (&PriceGraph{ 28 | pools: poolsMap, 29 | tokens: tokensMap, 30 | graph: NewGraph(), 31 | }).CalcGraph() 32 | } 33 | 34 | type PriceGraph struct { 35 | pools map[string]models.LiquidityPool 36 | tokens map[string]models.Token 37 | graph *Graph 38 | } 39 | 40 | func (g *PriceGraph) GetPrice(address string) (float64, float64, bool) { 41 | return 0, 0, false 42 | } 43 | func (g *PriceGraph) UpdatePrice(address string, value0 float64, value1 float64) *PriceGraph { 44 | if lp, ok := g.pools[address]; ok { 45 | _ = g.graph.SetWeight(EdgeKey(address, lp.Token0Address, lp.Token1Address), value0) 46 | _ = g.graph.SetWeight(EdgeKey(address, lp.Token1Address, lp.Token0Address), value1) 47 | } 48 | return g 49 | } 50 | func (g *PriceGraph) CalcGraph() *PriceGraph { 51 | g.graph = NewGraph() 52 | for addr, token := range g.tokens { 53 | g.graph.AddNode(*NewNode(addr, token.Symbol)) 54 | } 55 | for addr, pool := range g.pools { 56 | if err := g.graph.AddEdge(pool.Token0Address, pool.Token1Address, pool.Token0Price, addr, pool.Description, pool.Exchange); err != nil { 57 | return nil 58 | } 59 | if err := g.graph.AddEdge(pool.Token1Address, pool.Token0Address, pool.Token1Price, addr, pool.Description, pool.Exchange); err != nil { 60 | return nil 61 | } 62 | } 63 | return g 64 | } 65 | func (g PriceGraph) pathKey(edges ...*Edge) string { 66 | addresses := make([]string, len(edges)) 67 | for i, edge := range edges { 68 | addresses[i] = edge.Address 69 | } 70 | sort.Strings(addresses) 71 | return strings.Join(addresses, "/") 72 | } 73 | func (g *PriceGraph) ShortestPath(address string) ([]ArbPath, error) { 74 | opportunities := map[string]ArbPath{} 75 | node, err := g.graph.GetNode(address) 76 | if err != nil { 77 | return nil, err 78 | } 79 | for _, edge := range node.edgesFrom { 80 | weight := edge.Weight 81 | for _, e := range edge.To.edgesFrom { 82 | eWeight := weight * e.Weight 83 | if e.To.Address == node.Address { 84 | if edge.Address != e.Address && eWeight > directTreshold { 85 | pathKey := g.pathKey(edge, e) 86 | //fmt.Println("direct opportunity", edge.Name, e.Name, eWeight) 87 | arbPath := ArbPath{ 88 | StartAddress: edge.Address, 89 | StartExchange: edge.Exchange, 90 | Key: g.pathKey(edge, e), 91 | Path: []string{edge.Address, e.Address}, 92 | Prices: []float64{edge.Weight, e.Weight}, 93 | Tokens: []string{edge.From.Address, edge.To.Address}, 94 | Exchanges: []string{edge.Exchange, e.Exchange}, 95 | Weight: eWeight, 96 | } 97 | arbPath.Name = strings.Join([]string{edge.From.Name, edge.To.Name}, "->") 98 | opportunities[pathKey] = arbPath 99 | } else { 100 | continue 101 | } 102 | } 103 | for _, e2 := range e.To.edgesFrom { 104 | if e2.To.Address == node.Address { 105 | if eWeight*e2.Weight > triangularTreshold { 106 | edges := []*Edge{edge, e, e2} 107 | pathKey := g.pathKey(edge, e, e2) 108 | tokenNames := make([]string, len(edges)) 109 | arbPath := ArbPath{ 110 | StartAddress: edges[0].Address, 111 | StartExchange: edges[0].Exchange, 112 | Key: pathKey, 113 | Path: make([]string, len(edges)), 114 | Prices: make([]float64, len(edges)), 115 | Tokens: make([]string, len(edges)), 116 | Exchanges: make([]string, len(edges)), 117 | Weight: edge.Weight * e.Weight * e2.Weight, 118 | } 119 | for i, edge := range edges { 120 | arbPath.Path[i] = edge.Address 121 | arbPath.Prices[i] = edge.Weight 122 | arbPath.Tokens[i] = edge.From.Address 123 | arbPath.Exchanges[i] = edge.Exchange 124 | tokenNames[i] = edge.From.Name 125 | } 126 | arbPath.Name = strings.Join(tokenNames, "->") 127 | opportunities[pathKey] = arbPath 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | ret := make([]ArbPath, 0, len(opportunities)) 135 | for _, p := range opportunities { 136 | ret = append(ret, p) 137 | // fmt.Println("Opportunity!!!", edges[0].From.Name, edges[1].From.Name, edges[2].From.Name, edges[0].Weight*edges[1].Weight*edges[2].Weight, edges[0].Weight, edges[1].Weight, edges[2].Weight, time.Now()) 138 | } 139 | return ret, nil 140 | 141 | } 142 | 143 | type ArbPath struct { 144 | StartExchange string 145 | StartAddress string 146 | Key string 147 | Name string 148 | Weight float64 149 | Path []string 150 | Prices []float64 151 | Tokens []string 152 | Exchanges []string 153 | } 154 | -------------------------------------------------------------------------------- /tokens/lp_tokens.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/ethclient" 9 | "github.com/niccoloCastelli/defiarb/contracts/pancake_pair" 10 | "github.com/niccoloCastelli/defiarb/utils" 11 | "math/big" 12 | "time" 13 | ) 14 | 15 | var lpTokens = map[string]LpToken{} 16 | 17 | func init() { 18 | //AddLpTokenInfo("valueDefi", "0x8DD39f0a49160cDa5ef1E2a2fA7396EEc7DA8267", "vBSWAP/WBNB Value LP", "vBSWAPWBNB") 19 | } 20 | 21 | func NewLpTokenInfo(exchange string, address string, name string, code string, token0Address string, token1Address string) *LpToken { 22 | return &LpToken{ 23 | Erc20: &erc20{address: address, name: name, code: code}, 24 | exchange: exchange, 25 | token0Address: token0Address, 26 | token1Address: token1Address, 27 | } 28 | } 29 | 30 | type LpToken struct { 31 | Erc20 32 | exchange string 33 | token0Address string 34 | token1Address string 35 | } 36 | 37 | func (t *LpToken) Tokens(client *ethclient.Client) ([]Erc20, error) { 38 | instance, err := pancake_pair.NewPair(common.HexToAddress(t.Address()), client) 39 | if err != nil { 40 | return nil, err 41 | } 42 | symbol, _ := instance.Symbol(nil) 43 | name, _ := instance.Name(nil) 44 | reserves, err := instance.GetReserves(nil) 45 | fmt.Println("reserves", reserves, symbol, name) 46 | return nil, err 47 | } 48 | func (t *LpToken) TransactionHistory(ctx context.Context, client *ethclient.Client) error { 49 | lastBlock, err := client.BlockByNumber(ctx, nil) 50 | if err != nil { 51 | return err 52 | } 53 | lastBlockNumber := lastBlock.Number().Int64() 54 | pair, err := pancake_pair.NewPair(common.HexToAddress(t.Address()), client) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | for i := lastBlockNumber; i > (lastBlockNumber - 20000); i = i - 5000 { 60 | fromBlock := uint64(i - 5000) 61 | toBlock := uint64(i) 62 | logs, err := pair.FilterSwap(&bind.FilterOpts{ 63 | Start: fromBlock, 64 | End: &toBlock, 65 | }, nil, nil) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | for logs.Next() { 71 | if logs.Event == nil { 72 | break 73 | } 74 | block, err := client.BlockByNumber(ctx, big.NewInt(int64(logs.Event.Raw.BlockNumber))) 75 | if err != nil { 76 | return err 77 | } 78 | blockTime := time.Unix(int64(block.Time()), 0) 79 | if logs.Event.Amount0In.Uint64() != 0 { 80 | price := utils.FormatAmount(logs.Event.Amount0In, 18) / utils.FormatAmount(logs.Event.Amount1Out, 18) 81 | fmt.Println("swap 0 -> 1", price, utils.FormatAmount(logs.Event.Amount0In, 18), utils.FormatAmount(logs.Event.Amount1Out, 18), blockTime) 82 | } else { 83 | price := utils.FormatAmount(logs.Event.Amount0Out, 18) / utils.FormatAmount(logs.Event.Amount1In, 18) 84 | fmt.Println("swap 1 -> 0", price, utils.FormatAmount(logs.Event.Amount1In, 18), utils.FormatAmount(logs.Event.Amount0Out, 18), blockTime) 85 | } 86 | 87 | } 88 | } 89 | return nil 90 | } 91 | func AddLpTokenInfo(exchange string, address string, name string, code string) { 92 | lpTokens[address] = *NewLpTokenInfo(exchange, address, name, code, "", "") 93 | } 94 | 95 | func SupportedLpTokens() []LpToken { 96 | ret := make([]LpToken, 0, len(lpTokens)) 97 | for _, tokenInfo := range lpTokens { 98 | ret = append(ret, tokenInfo) 99 | } 100 | return ret 101 | } 102 | -------------------------------------------------------------------------------- /tokens/supported_tokens.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | var supportedTokens []Erc20 4 | 5 | // Default BSC tokens 6 | func init() { 7 | supportedTokens = []Erc20{ 8 | NewErc20("0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", "Wrapped BNB", "WBNB", "https://bscscan.com/token/images/binance_32.png"), 9 | 10 | NewErc20("0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c", "Binance-Peg BTC", "BTCB", "https://bscscan.com/token/images/btcb_32.png"), 11 | NewErc20("0x2170ed0880ac9a755fd29b2688956bd959f933f8", "Binance-Peg Ethereum", "ETH", "https://bscscan.com/token/images/ethereum_32.png"), 12 | NewErc20("0xe9e7cea3dedca5984780bafc599bd69add087d56", "Binance-Peg BUSD", "BUSD", "https://bscscan.com/token/images/busd_32.png"), 13 | NewErc20("0x55d398326f99059ff775485246999027b3197955", "Binance-Peg BUSD-T", "USDT", "https://bscscan.com/token/images/busdt_32.png"), 14 | NewErc20("0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", "Binance-Peg Dai Token", "DAI", "https://bscscan.com/token/images/dai_32.png"), 15 | NewErc20("0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", "Binance-Peg USD Coin", "USDC", "https://bscscan.com/token/images/centre-usdc_28.png"), 16 | NewErc20("0x7083609fce4d1d8dc0c979aab8c869ea2c873402", "Binance-Peg Polkadot", "DOT", "https://bscscan.com/token/images/polkadot_32.png"), 17 | NewErc20("0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd", "Binance-Peg ChainLink", "LINK", "https://bscscan.com/token/images/chainlink_32.png?v=2"), 18 | NewErc20("0x101d82428437127bf1608f699cd651e6abf9766e", "Binance-Peg Basic Attention Token", "BAT", "https://bscscan.com/token/images/bat_32.png"), 19 | NewErc20("0x947950BcC74888a40Ffa2593C5798F11Fc9124C4", "Binance-Peg SushiToken", "SUSHI", "https://bscscan.com/token/images/sushiswap_32.png"), 20 | NewErc20("0x4338665CBB7B2485A8855A139b75D5e34AB0DB94", "Binance-Peg Litecoin", "LTC", "https://bscscan.com/token/images/litecoin_32.png"), 21 | NewErc20("0x250632378e573c6be1ac2f97fcdf00515d0aa91b", "Binance Beacon ETH", "BETH", "https://bscscan.com/token/images/binance-beth_32.png"), 22 | 23 | NewErc20("0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82", "PancakeSwap Token", "CAKE", "https://bscscan.com/token/images/pancake_32.png?=v1"), 24 | NewErc20("0x4bd17003473389a42daf6a0a729f6fdb328bbbd7", "VAI Stablecoin", "VAI", "https://bscscan.com/token/images/venus-vai_32.png"), 25 | 26 | NewErc20("0xe0e514c71282b6f4e823703a39374cf58dc3ea4f", "BELT Token", "BELT", "https://bscscan.com/token/images/beltfinance_32.png"), 27 | NewErc20("0x603c7f932ed1fc6575303d8fb018fdcbb0f39a95", "ApeSwapFinance Banana", "BANANA", "https://bscscan.com/token/images/apeswap_32.png"), 28 | NewErc20("0xa184088a740c695e156f91f5cc086a06bb78b827", "AUTOv2", "AUTO", "https://bscscan.com/token/images/autofarm_32.png"), 29 | NewErc20("0xbcf39f0edda668c58371e519af37ca705f2bfcbd", "PolyCrowns", "pCWS", "https://bscscan.com/token/images/seascape_32.png"), 30 | NewErc20("0xCa3F508B8e4Dd382eE878A314789373D80A5190A", "beefy.finance", "BIFI", "https://bscscan.com/token/images/beefy_32.png?=v1"), 31 | } 32 | } 33 | 34 | func SupportedTokens() []Erc20 { 35 | cp := make([]Erc20, len(supportedTokens)) 36 | copy(cp, supportedTokens) 37 | return cp 38 | } 39 | -------------------------------------------------------------------------------- /tokens/token_info.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/ethereum/go-ethereum/ethclient" 6 | "github.com/niccoloCastelli/defiarb/contracts/bep20" 7 | "math" 8 | ) 9 | 10 | type Erc20 interface { 11 | LogoUrl() string 12 | Code() string 13 | Name() string 14 | Address() string 15 | BalanceOf(client *ethclient.Client, account common.Address) (uint64, error) 16 | FormattedBalance(client *ethclient.Client, account common.Address) (float64, error) 17 | } 18 | 19 | func NewErc20(address string, name string, code string, logoUrl string) Erc20 { 20 | return &erc20{address: address, name: name, code: code, logoUrl: logoUrl} 21 | } 22 | 23 | type erc20 struct { 24 | address string 25 | name string 26 | code string 27 | logoUrl string 28 | } 29 | 30 | func (t erc20) LogoUrl() string { 31 | return t.logoUrl 32 | } 33 | func (t erc20) Code() string { 34 | return t.code 35 | } 36 | func (t erc20) Name() string { 37 | return t.name 38 | } 39 | func (t erc20) Address() string { 40 | return t.address 41 | } 42 | func (t erc20) BalanceOf(client *ethclient.Client, account common.Address) (uint64, error) { 43 | instance, err := bep20.NewBep20(common.HexToAddress(t.address), client) 44 | if err != nil { 45 | return 0, err 46 | } 47 | tokenBalance, err := instance.BalanceOf(nil, account) 48 | if err != nil { 49 | return 0, err 50 | } 51 | return tokenBalance.Uint64(), nil 52 | } 53 | func (t erc20) FormattedBalance(client *ethclient.Client, account common.Address) (float64, error) { 54 | instance, err := bep20.NewBep20(common.HexToAddress(t.address), client) 55 | balance, err := t.BalanceOf(client, account) 56 | if err != nil { 57 | return 0, err 58 | } 59 | decimals, err := instance.Decimals(nil) 60 | if err != nil { 61 | return 0, err 62 | } 63 | return float64(balance) / math.Pow(10, float64(decimals)), nil 64 | } 65 | -------------------------------------------------------------------------------- /utils/db_types.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | type BigInt struct { 10 | big.Int 11 | } 12 | 13 | func NewBigInt(val *big.Int) *BigInt { 14 | if val == nil { 15 | return nil 16 | } 17 | return &BigInt{Int: *val} 18 | } 19 | 20 | func (b *BigInt) Value() (driver.Value, error) { 21 | if b != nil { 22 | return b.String(), nil 23 | } 24 | return nil, nil 25 | } 26 | 27 | func (b *BigInt) Scan(value interface{}) error { 28 | if value == nil { 29 | b = nil 30 | } 31 | switch t := value.(type) { 32 | case int64: 33 | b.Int = *big.NewInt(value.(int64)) 34 | case []uint8: 35 | b.Int = *big.NewInt(0).SetBytes(t) 36 | default: 37 | return fmt.Errorf("Could not scan type %T into BigInt", t) 38 | } 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /utils/errors.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "github.com/pkg/errors" 6 | "strings" 7 | ) 8 | 9 | func WrapErrors(errs ...error) error { 10 | if len(errs) == 0 { 11 | return nil 12 | } 13 | combinedErr := make([]string, len(errs)) 14 | for i, err := range errs { 15 | combinedErr[i] = fmt.Sprintf("%d. %s", i, err.Error()) 16 | } 17 | return errors.New(strings.Join(combinedErr, "\n")) 18 | } 19 | -------------------------------------------------------------------------------- /utils/format.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | func FormatAmount(val *big.Int, decimals uint8) float64 { 8 | floatVal := new(big.Float).SetInt(val) 9 | exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil) 10 | result, _ := new(big.Float).Quo(floatVal, new(big.Float).SetInt(exp)).Float64() 11 | return result 12 | } 13 | 14 | func GetAmount(baseAmount int64, decimals int64) *big.Int { 15 | return new(big.Int).Mul(big.NewInt(baseAmount), new(big.Int).Exp(big.NewInt(10), big.NewInt(decimals), nil)) 16 | } 17 | -------------------------------------------------------------------------------- /utils/system.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "os" 4 | 5 | func GetEnv(env string, defaultVal string) string { 6 | if val := os.Getenv(env); val != "" { 7 | return val 8 | } 9 | return defaultVal 10 | } 11 | --------------------------------------------------------------------------------