github.com/cwntr/go-defi@v0.0.0-20210629134751-07f9ec2f7e66/client/client.go (about) 1 package client 2 3 import ( 4 "context" 5 "encoding/hex" 6 "fmt" 7 "math/big" 8 "strings" 9 10 "github.com/524119574/go-defi/binding/haave" 11 "github.com/524119574/go-defi/binding/hbalancer_exchange" 12 "github.com/524119574/go-defi/binding/hcether" 13 "github.com/524119574/go-defi/binding/hctoken" 14 "github.com/524119574/go-defi/binding/hcurve" 15 "github.com/524119574/go-defi/binding/hkyber" 16 "github.com/524119574/go-defi/binding/hmaker" 17 "github.com/524119574/go-defi/binding/huniswap" 18 "github.com/524119574/go-defi/binding/hyearn" 19 20 "github.com/524119574/go-defi/binding/herc20tokenin" 21 "github.com/ethereum/go-ethereum/accounts/abi" 22 23 "github.com/524119574/go-defi/binding/aave/lendingpool" 24 ceth_binding "github.com/524119574/go-defi/binding/compound/cETH" 25 "github.com/524119574/go-defi/binding/compound/cToken" 26 "github.com/524119574/go-defi/binding/erc20" 27 "github.com/524119574/go-defi/binding/furucombo" 28 "github.com/524119574/go-defi/binding/swapper" 29 "github.com/524119574/go-defi/binding/uniswap" 30 "github.com/524119574/go-defi/binding/yearn/yregistry" 31 "github.com/524119574/go-defi/binding/yearn/yvault" 32 "github.com/524119574/go-defi/binding/yearn/yweth" 33 "github.com/ethereum/go-ethereum/accounts/abi/bind" 34 "github.com/ethereum/go-ethereum/common" 35 "github.com/ethereum/go-ethereum/core/types" 36 "github.com/ethereum/go-ethereum/ethclient" 37 ) 38 39 type rateModel int64 40 41 const ( 42 // StableRate https://medium.com/aave/aave-borrowing-rates-upgraded-f6c8b27973a7 43 StableRate rateModel = 1 44 // VariableRate https://medium.com/aave/aave-borrowing-rates-upgraded-f6c8b27973a7 45 VariableRate rateModel = 2 46 ) 47 48 type coinType int 49 50 const ( 51 // ETH is ether. 52 ETH coinType = iota 53 // BAT is basic attention token. 54 BAT coinType = iota 55 // COMP is the governance token for Compound. 56 COMP coinType = iota 57 // DAI is the stable coin. 58 DAI coinType = iota 59 // REP is Augur reputation token. 60 REP coinType = iota 61 // SAI is Single Collateral DAI. 62 SAI coinType = iota 63 // UNI is the governance token for Uniswap. 64 UNI coinType = iota 65 // USDC is the stable coin by Circle. 66 USDC coinType = iota 67 // USDT is the stable coin. 68 USDT coinType = iota 69 // WBTC is wrapped BTC. 70 WBTC coinType = iota 71 // ZRX is the utility token for 0x. 72 ZRX coinType = iota 73 // BUSD is the Binance USD token. 74 BUSD coinType = iota 75 // YFI is the yearn governance token. 76 YFI coinType = iota 77 // AAVE is the Aave governance token. 78 AAVE coinType = iota 79 80 // cToken is the token that user receive after deposit into Yearn 81 cETH = iota 82 83 cDAI = iota 84 85 cUSDC = iota 86 87 yWETH = iota 88 ) 89 90 const ( 91 // uniswapAddr is UniswapV2Router, see here: https://uniswap.org/docs/v2/smart-contracts/router02/#address 92 uniswapAddr string = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D" 93 yRegistryAddr string = "0x3eE41C098f9666ed2eA246f4D2558010e59d63A0" 94 yETHVaultAddr string = "0xe1237aA7f535b0CC33Fd973D66cBf830354D16c7" 95 aaveLendingPoolAddr string = "0x398eC7346DcD622eDc5ae82352F02bE94C62d119" 96 aaveLendingPoolCoreAddr string = "0x3dfd23A6c5E8BbcFc9581d2E864a68feb6a076d3" 97 // Proxy and Handler related addresses 98 99 // ProxyAddr is the address of the proxy contract. 100 ProxyAddr string = "0x57805e5a227937bac2b0fdacaa30413ddac6b8e1" 101 hCEtherAddr string = "0x9A1049f7f87Dbb0468C745d9B3952e23d5d6CE5e" 102 hErcInAddr string = "0x914490a362f4507058403a99e28bdf685c5c767f" 103 hCTokenAddr string = "0x8973D623d883c5641Dd3906625Aac31cdC8790c5" 104 hMakerDaoAddr string = "0x294fbca49c8a855e04d7d82b28256b086d39afea" 105 hUniswapAddr string = "0x58a21cfcee675d65d577b251668f7dc46ea9c3a0" 106 hCurveAddr string = "0xa36dfb057010c419c5917f3d68b4520db3671cdb" 107 hYearnAddr string = "0xC50C8F34c9955217a6b3e385a069184DCE17fD2A" 108 hAaveAddr string = "0xf579b009748a62b1978639d6b54259f8dc915229" 109 hOneInch string = "0x783f5c56e3c8b23d90e4a271d7acbe914bfcd319" 110 hFunds string = "0xf9b03e9ea64b2311b0221b2854edd6df97669c09" 111 hKyberAddr string = "0xe2a3431508cd8e72d53a0e4b57c24af2899322a0" 112 hBalancerExchangeAddr string = "0x892dD6ebd2e3E1c0D6592309bA82a0095830D6d6" 113 114 // TODO: The following is not on mainnet yet 115 hSushiswapAddr string = "0xB6F469a8930dd5111c0EA76571c7E86298A171f7" 116 hSwapper string = "0x017F3f2EB0c55DDF49B95ad38Cd2737ACf64AB4d" 117 118 // Curve pool addresses 119 cCompound string = "0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56" 120 cUsdt string = "0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C" 121 cY string = "0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51" 122 cBusd string = "0x79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27" 123 cSusd string = "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD" 124 cRen string = "0x93054188d876f558f4a66B2EF1d97d16eDf0895B" 125 cSbtc string = "0x7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714" 126 cHbtc string = "0x4ca9b3063ec5866a4b82e437059d2c43d1be596f" 127 c3Pool string = "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7" 128 cGusd string = "0x4f062658eaaf2c1ccf8c8e36d6824cdf41167956" 129 cHusd string = "0x3eF6A01A0f81D6046290f3e2A8c5b843e738E604" 130 cUsdk string = "0x3e01dd8a5e1fb3481f0f589056b428fc308af0fb" 131 cUsdn string = "0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1" 132 133 // Curve token addresses 134 compCrv string = "0x845838DF265Dcd2c412A1Dc9e959c7d08537f8a2" 135 usdtCrv string = "0x9fC689CCaDa600B6DF723D9E47D84d76664a1F23" 136 yCrv string = "0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8" 137 busdCrv string = "0x3B3Ac5386837Dc563660FB6a0937DFAa5924333B" 138 susdCrv string = "0xC25a3A3b969415c80451098fa907EC722572917F" 139 renCrv string = "0x49849C98ae39Fff122806C06791Fa73784FB3675" 140 sbtcCrv string = "0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3" 141 hbtcCrv string = "0xb19059ebb43466C323583928285a49f558E572Fd" 142 threePoolCrv string = "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" 143 gusdCrv string = "0xD2967f45c4f384DEEa880F807Be904762a3DeA07" 144 husdCrv string = "0x5B5CFE992AdAC0C9D48E05854B2d91C73a003858" 145 usdkCrv string = "0x97E2768e8E73511cA874545DC5Ff8067eB19B787" 146 usdnCrv string = "0x4f3E8F405CF5aFC05D68142F3783bDfE13811522" 147 ) 148 149 // CoinToAddressMap returns a mapping from coin to address 150 var CoinToAddressMap = map[coinType]common.Address{ 151 ETH: common.HexToAddress("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), 152 BAT: common.HexToAddress("0x0d8775f648430679a709e98d2b0cb6250d2887ef"), 153 COMP: common.HexToAddress("0xc00e94cb662c3520282e6f5717214004a7f26888"), 154 DAI: common.HexToAddress("0x6b175474e89094c44da98b954eedeac495271d0f"), 155 USDC: common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), 156 USDT: common.HexToAddress("0xdac17f958d2ee523a2206206994597c13d831ec7"), 157 cETH: common.HexToAddress("0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5"), 158 cDAI: common.HexToAddress("0x5d3a536e4d6dbd6114cc1ead35777bab948e3643"), 159 cUSDC: common.HexToAddress("0x39aa39c021dfbae8fac545936693ac917d5e7563"), 160 BUSD: common.HexToAddress("0x4Fabb145d64652a948d72533023f6E7A623C7C53"), 161 yWETH: common.HexToAddress("0xe1237aA7f535b0CC33Fd973D66cBf830354D16c7"), 162 } 163 164 // CoinToCompoundMap returns a mapping from coin to compound address 165 var CoinToCompoundMap = map[coinType]common.Address{ 166 ETH: common.HexToAddress("0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5"), 167 DAI: common.HexToAddress("0x5d3a536e4d6dbd6114cc1ead35777bab948e3643"), 168 USDC: common.HexToAddress("0x39aa39c021dfbae8fac545936693ac917d5e7563"), 169 } 170 171 // CoinToJoinMap maps the coin type to its corresponding Join which is a MakerDao terminology meaning an adapter to 172 // deposit and withdraw unlocked collateral. 173 var CoinToJoinMap = map[coinType]common.Address{ 174 DAI: common.HexToAddress("0x9759A6Ac90977b93B58547b4A71c78317f391A28"), 175 ETH: common.HexToAddress("0x2F0b23f53734252Bda2277357e97e1517d6B042A"), 176 USDC: common.HexToAddress("0x2600004fd1585f7270756DDc88aD9cfA10dD0428"), 177 YFI: common.HexToAddress("0x3ff33d9162aD47660083D7DC4bC02Fb231c81677"), 178 USDT: common.HexToAddress("0x0Ac6A1D74E84C2dF9063bDDc31699FF2a2BB22A2"), 179 UNI: common.HexToAddress("0x2502F65D77cA13f183850b5f9272270454094A08"), 180 AAVE: common.HexToAddress("0x24e459F61cEAa7b1cE70Dbaea938940A7c5aD46e"), 181 } 182 183 // CoinToIlkMap maps the coin type to the corresponding Ilk as found in here: 184 // https://etherscan.io/address/0x8b4ce5DCbb01e0e1f0521cd8dCfb31B308E52c24 185 // Ilk is a MakerDao collateral type, each Ilk correspond to a type of collateral and 186 // user can query it's name, symbol, dec, gem, pip, join and flip. 187 var CoinToIlkMap = map[coinType][32]byte{ 188 ETH: byte32PutString("4554482d41000000000000000000000000000000000000000000000000000000"), 189 YFI: byte32PutString("5946492d41000000000000000000000000000000000000000000000000000000"), 190 USDC: byte32PutString("555344432d420000000000000000000000000000000000000000000000000000"), 191 USDT: byte32PutString("555344542d410000000000000000000000000000000000000000000000000000"), 192 UNI: byte32PutString("554e4956324441494554482d4100000000000000000000000000000000000000"), 193 AAVE: byte32PutString("414156452d410000000000000000000000000000000000000000000000000000"), 194 } 195 196 // Client is the new interface 197 type Client interface { 198 Uniswap() UniswapClient 199 } 200 201 // NewClient Create a new client 202 // opts can be created using your private key 203 // ethclient can be created when you dial an ETH end point 204 func NewClient(opts *bind.TransactOpts, ethClient *ethclient.Client) *DefiClient { 205 c := new(DefiClient) 206 c.conn = ethClient 207 c.opts = opts 208 return c 209 } 210 211 // DefiClient is the struct that stores the information. 212 type DefiClient struct { 213 opts *bind.TransactOpts 214 conn *ethclient.Client 215 } 216 217 // BalanceOf returns the balance of a given coin. 218 func (c *DefiClient) BalanceOf(coin coinType) (*big.Int, error) { 219 return c.balanceOf(CoinToAddressMap[coin]) 220 } 221 222 func (c *DefiClient) balanceOf(addr common.Address) (*big.Int, error) { 223 erc20, err := erc20.NewErc20(addr, c.conn) 224 if err != nil { 225 return nil, err 226 } 227 balance, err := erc20.BalanceOf(nil, c.opts.From) 228 if err != nil { 229 return nil, err 230 } 231 232 return balance, nil 233 } 234 235 // ExecuteActions sends one transaction for all the Defi interactions. 236 func (c *DefiClient) ExecuteActions(actions *Actions) error { 237 gasPrice, err := c.SuggestGasPrice(nil) 238 if err != nil { 239 return err 240 } 241 242 return c.ExecuteActionsWithGasPrice(actions, gasPrice) 243 } 244 245 // SuggestGasPrice provides an estimation of the gas price based on the `blockNum`. 246 // If the blockNum is `nil`, it will automatically use the latest block data. 247 // The user can also specify a specific `blockNum` so that block will be used for the prediction. 248 func (c *DefiClient) SuggestGasPrice(blockNum *big.Int) (*big.Int, error) { 249 if blockNum == nil { 250 header, err := c.conn.HeaderByNumber(context.Background(), nil) 251 if err != nil { 252 return nil, err 253 } 254 blockNum = header.Number 255 } 256 257 block, err := c.conn.BlockByNumber(context.Background(), blockNum) 258 if err != nil { 259 return nil, err 260 } 261 262 // If there is no transaction in the current block we fail back to the previous block. 263 if block.Transactions().Len() == 0 { 264 prvBlock := big.NewInt(0) 265 prvBlock.Sub(blockNum, big.NewInt(1)) 266 return c.SuggestGasPrice(prvBlock) 267 } 268 269 sum := big.NewInt(0) 270 for _, transaction := range block.Transactions() { 271 sum.Add(sum, transaction.GasPrice()) 272 } 273 274 size := big.NewInt(int64(len(block.Transactions()))) 275 average := big.NewInt(0) 276 277 average.Div(sum, size) 278 return average, nil 279 } 280 281 // ExecuteActionsWithGasPrice sends one transaction for all the Defi interactions with given gasPrice. 282 func (c *DefiClient) ExecuteActionsWithGasPrice(actions *Actions, gasPrice *big.Int) error { 283 handlers, datas, totalEthers, err := c.CombineActions(actions) 284 285 if err != nil { 286 return err 287 } 288 289 proxy, err := furucombo.NewFurucombo(common.HexToAddress(ProxyAddr), c.conn) 290 if err != nil { 291 return nil 292 } 293 294 opts := &bind.TransactOpts{ 295 Value: totalEthers, 296 Signer: c.opts.Signer, 297 From: c.opts.From, 298 GasLimit: 5000000, 299 GasPrice: gasPrice, 300 } 301 tx, err := proxy.BatchExec(opts, handlers, datas) 302 if err != nil { 303 return nil 304 } 305 receipt, err := bind.WaitMined(context.Background(), c.conn, tx) 306 if err != nil { 307 return err 308 } 309 if receipt.Status != 1 { 310 return fmt.Errorf("tx receipt status is not 1, indicating a failure occurred") 311 } 312 return nil 313 } 314 315 // CombineActions takes in an `Actions` and returns a slice of handler address and a slice of call data 316 // if the combine is not successful, it will return the error. 317 func (c *DefiClient) CombineActions(actions *Actions) ([]common.Address, [][]byte, *big.Int, error) { 318 handlers := []common.Address{} 319 datas := make([][]byte, 0) 320 totalEthers := big.NewInt(0) 321 approvalTokens := make([]common.Address, 0) 322 approvalAmounts := make([]*big.Int, 0) 323 324 for i := 0; i < len(actions.Actions); i++ { 325 handlers = append(handlers, actions.Actions[i].handlerAddr) 326 datas = append(datas, actions.Actions[i].data) 327 totalEthers.Add(totalEthers, actions.Actions[i].ethersNeeded) 328 if len(actions.Actions[i].approvalTokens) > 0 { 329 for j := 0; j < len(actions.Actions[i].approvalTokens); j++ { 330 tokenAddr := actions.Actions[i].approvalTokens[j] 331 tokenAmount := actions.Actions[i].approvalTokenAmounts[j] 332 approvalTokens = append(approvalTokens, tokenAddr) 333 balance, err := c.balanceOf(tokenAddr) 334 if err != nil { 335 return nil, nil, nil, err 336 } 337 if balance.Cmp(tokenAmount) == 1 { 338 approvalAmounts = append(approvalAmounts, tokenAmount) 339 } else { 340 approvalAmounts = append(approvalAmounts, balance) 341 } 342 } 343 344 } 345 } 346 347 if len(approvalTokens) > 0 { 348 parsed, err := abi.JSON(strings.NewReader(herc20tokenin.Herc20tokeninABI)) 349 if err != nil { 350 return nil, nil, nil, err 351 } 352 injectData, err := parsed.Pack("inject", approvalTokens, approvalAmounts) 353 354 if err != nil { 355 return nil, nil, nil, err 356 } 357 358 handlers = append([]common.Address{common.HexToAddress(hFunds)}, handlers...) 359 datas = append([][]byte{injectData}, datas...) 360 } 361 362 return handlers, datas, totalEthers, nil 363 } 364 365 // SupplyFundActions transfer a certain amount of fund to the proxy 366 func (c *DefiClient) SupplyFundActions(size *big.Int, coin coinType) *Actions { 367 parsed, err := abi.JSON(strings.NewReader(herc20tokenin.Herc20tokeninABI)) 368 if err != nil { 369 return nil 370 } 371 injectData, err := parsed.Pack( 372 "inject", []common.Address{CoinToAddressMap[coin]}, []*big.Int{size}) 373 374 if err != nil { 375 return nil 376 } 377 378 return &Actions{ 379 Actions: []action{ 380 { 381 handlerAddr: common.HexToAddress(hFunds), 382 data: injectData, 383 ethersNeeded: big.NewInt(0), 384 }, 385 }, 386 } 387 } 388 389 // action represents one action, e.g. supply to Compound, swap on Uniswap 390 type action struct { 391 handlerAddr common.Address 392 data []byte 393 ethersNeeded *big.Int 394 // There could be multiple tokens that we need to approve in the case of say Curve add liquidity or Flash loan 395 approvalTokens []common.Address 396 approvalTokenAmounts []*big.Int 397 } 398 399 // Actions represents a list of Action. 400 type Actions struct { 401 Actions []action 402 } 403 404 // Add adds actions together 405 // This is a variadic function so user can pass in any number of actions. 406 func (actions *Actions) Add(newActionss ...*Actions) error { 407 if newActionss == nil { 408 return fmt.Errorf("new action is nil") 409 } 410 for _, newActions := range newActionss { 411 actions.Actions = append(actions.Actions, newActions.Actions...) 412 } 413 return nil 414 } 415 416 // Uniswap--------------------------------------------------------------------- 417 418 // UniswapClient struct 419 type UniswapClient struct { 420 client *DefiClient 421 uniswap *uniswap.Uniswap 422 } 423 424 // Uniswap returns a uniswap client. 425 func (c *DefiClient) Uniswap() *UniswapClient { 426 uniClient := new(UniswapClient) 427 uniClient.client = c 428 uniswap, err := uniswap.NewUniswap(common.HexToAddress(uniswapAddr), c.conn) 429 430 if err != nil { 431 return nil 432 } 433 434 uniClient.uniswap = uniswap 435 return uniClient 436 } 437 438 // TxHash represents a transaction hash. 439 type TxHash string 440 441 // Swap in the Uniswap Exchange. 442 func (c *UniswapClient) Swap(size int64, baseCurrency coinType, quoteCurrency coinType, receipient common.Address) error { 443 if quoteCurrency == ETH { 444 return c.swapETHToToken(size, baseCurrency, receipient) 445 } else { 446 err := Approve(c.client, quoteCurrency, common.HexToAddress(uniswapAddr), big.NewInt(size)) 447 if err != nil { 448 return err 449 } 450 if baseCurrency == ETH { 451 return c.swapTokenToETH(size, quoteCurrency, receipient) 452 } else { 453 return c.swapTokenToToken(size, baseCurrency, quoteCurrency, receipient) 454 } 455 } 456 } 457 458 func (c *UniswapClient) swapETHToToken(size int64, baseCurrency coinType, receipient common.Address) error { 459 path := []common.Address{CoinToAddressMap[ETH], CoinToAddressMap[baseCurrency]} 460 tx, err := c.uniswap.SwapExactETHForTokens( 461 // TODO: there is basically no minimum output amount set, so this could cause huge slippage, need to fix. 462 // Also the time stamp is set to 2038 January 1, it's better to set it dynamically. 463 &bind.TransactOpts{ 464 Value: big.NewInt(size), 465 Signer: c.client.opts.Signer, 466 From: c.client.opts.From, 467 GasLimit: 500000, 468 GasPrice: big.NewInt(20000000000), 469 }, 470 big.NewInt(0), 471 path, receipient, 472 big.NewInt(2145916800), 473 ) 474 if err != nil { 475 return err 476 } 477 bind.WaitMined(context.Background(), c.client.conn, tx) 478 return nil 479 } 480 481 func (c *UniswapClient) swapTokenToToken(size int64, baseCurrency coinType, quoteCurrency coinType, receipient common.Address) error { 482 path := []common.Address{CoinToAddressMap[quoteCurrency], CoinToAddressMap[ETH], CoinToAddressMap[baseCurrency]} 483 484 tx, err := c.uniswap.SwapExactTokensForTokens( 485 // TODO: there is basically no minimum output amount set, so this could cause huge slippage, need to fix. 486 // Also the time stamp is set to 2038 January 1, it's better to set it dynamically. 487 c.client.opts, big.NewInt(size), big.NewInt(0), path, receipient, big.NewInt(2145916800)) 488 if err != nil { 489 return err 490 } 491 bind.WaitMined(context.Background(), c.client.conn, tx) 492 return nil 493 } 494 495 func (c *UniswapClient) swapTokenToETH(size int64, quoteCurrency coinType, receipient common.Address) error { 496 path := []common.Address{CoinToAddressMap[quoteCurrency], CoinToAddressMap[ETH]} 497 498 tx, err := c.uniswap.SwapExactTokensForETH( 499 // TODO: there is basically no minimum output amount set, so this could cause huge slippage, need to fix. 500 // Also the time stamp is set to 2038 January 1, it's better to set it dynamically. 501 c.client.opts, big.NewInt(size), big.NewInt(0), path, receipient, big.NewInt(2145916800)) 502 if err != nil { 503 return err 504 } 505 bind.WaitMined(context.Background(), c.client.conn, tx) 506 return nil 507 } 508 509 // SwapActions create a new swap action. 510 func (c *UniswapClient) SwapActions(size *big.Int, baseCurrency coinType, quoteCurrency coinType) *Actions { 511 var callData []byte 512 var ethersNeeded = big.NewInt(0) 513 if quoteCurrency == ETH { 514 ethersNeeded = size 515 callData = swapETHToTokenData(size, baseCurrency) 516 } else { 517 if baseCurrency == ETH { 518 callData = swapTokenToETHData(size, quoteCurrency) 519 } else { 520 callData = swapTokenToTokenData(size, baseCurrency, quoteCurrency) 521 } 522 } 523 524 return &Actions{ 525 Actions: []action{ 526 { 527 handlerAddr: common.HexToAddress(hUniswapAddr), 528 data: callData, 529 ethersNeeded: ethersNeeded, 530 }, 531 }, 532 } 533 } 534 535 func swapETHToTokenData(size *big.Int, baseCurrency coinType) []byte { 536 parsed, err := abi.JSON(strings.NewReader(huniswap.HuniswapABI)) 537 if err != nil { 538 return nil 539 } 540 data, err := parsed.Pack( 541 "swapExactETHForTokens", size, big.NewInt(0), []common.Address{CoinToAddressMap[ETH], CoinToAddressMap[baseCurrency]}) 542 if err != nil { 543 return nil 544 } 545 return data 546 } 547 548 func swapTokenToETHData(size *big.Int, quoteCurrency coinType) []byte { 549 parsed, err := abi.JSON(strings.NewReader(huniswap.HuniswapABI)) 550 if err != nil { 551 return nil 552 } 553 data, err := parsed.Pack( 554 "swapExactTokensForETH", size, big.NewInt(0), []common.Address{CoinToAddressMap[quoteCurrency], CoinToAddressMap[ETH]}) 555 if err != nil { 556 return nil 557 } 558 return data 559 } 560 561 func swapTokenToTokenData(size *big.Int, baseCurrency coinType, quoteCurrency coinType) []byte { 562 parsed, err := abi.JSON(strings.NewReader(huniswap.HuniswapABI)) 563 if err != nil { 564 return nil 565 } 566 data, err := parsed.Pack( 567 "swapExactTokensForTokens", size, big.NewInt(0), []common.Address{ 568 CoinToAddressMap[quoteCurrency], CoinToAddressMap[ETH], CoinToAddressMap[baseCurrency]}) 569 if err != nil { 570 return nil 571 } 572 return data 573 } 574 575 // FlashSwapActions create an action to perform flash swap on Uniswap. 576 func (c *UniswapClient) FlashSwapActions(size *big.Int, coinBorrow coinType, coinRepay coinType, actions *Actions) *Actions { 577 handlers := []common.Address{} 578 datas := make([][]byte, 0) 579 totalEthers := big.NewInt(0) 580 for i := 0; i < len(actions.Actions); i++ { 581 handlers = append(handlers, actions.Actions[i].handlerAddr) 582 datas = append(datas, actions.Actions[i].data) 583 totalEthers.Add(totalEthers, actions.Actions[i].ethersNeeded) 584 } 585 586 proxy, err := abi.JSON(strings.NewReader(furucombo.FurucomboABI)) 587 if err != nil { 588 return nil 589 } 590 payloadData, err := proxy.Pack("execs", handlers, datas) 591 if err != nil { 592 return nil 593 } 594 swapperAbi, err := abi.JSON(strings.NewReader(swapper.SwapperABI)) 595 if err != nil { 596 return nil 597 } 598 // skip the first 4 bytes to omit the function selector 599 flashSwapData, err := swapperAbi.Pack("startSwap", CoinToAddressMap[coinBorrow], size, CoinToAddressMap[coinRepay], payloadData[4:]) 600 if err != nil { 601 return nil 602 } 603 604 return &Actions{ 605 Actions: []action{ 606 { 607 handlerAddr: common.HexToAddress(hSwapper), 608 data: flashSwapData, 609 ethersNeeded: totalEthers, 610 }, 611 }, 612 } 613 } 614 615 // Compound--------------------------------------------------------------------- 616 617 // CompoundClient is an instance of Compound protocol. 618 type CompoundClient struct { 619 client *DefiClient 620 } 621 622 // Compound returns a compound client. 623 func (c *DefiClient) Compound() *CompoundClient { 624 compoundClient := new(CompoundClient) 625 compoundClient.client = c 626 627 return compoundClient 628 } 629 630 // Supply supplies token to compound. 631 func (c *CompoundClient) Supply(amount int64, coin coinType) error { 632 var ( 633 tx *types.Transaction 634 err error 635 ) 636 637 cTokenAddr, err := c.getPoolAddrFromCoin(coin) 638 opts := &bind.TransactOpts{ 639 From: c.client.opts.From, 640 Signer: c.client.opts.Signer, 641 GasLimit: 500000, 642 GasPrice: big.NewInt(20000000000), 643 } 644 645 switch coin { 646 case ETH: 647 opts.Value = big.NewInt(amount) 648 cETHContract, err := ceth_binding.NewCETH(cTokenAddr, c.client.conn) 649 if err != nil { 650 return err 651 } 652 653 tx, err = cETHContract.Mint(opts) 654 case BAT, COMP, DAI, REP, SAI, UNI, USDC, USDT, WBTC, ZRX: 655 err = Approve(c.client, coin, cTokenAddr, big.NewInt(amount)) 656 if err != nil { 657 return err 658 } 659 cTokenContract, err := cToken.NewCToken(cTokenAddr, c.client.conn) 660 if err != nil { 661 return err 662 } 663 tx, err = cTokenContract.Mint(opts, big.NewInt(amount)) 664 default: 665 return fmt.Errorf("Not supported") 666 } 667 668 if err != nil { 669 fmt.Printf("Error mint ctoken: %v", err) 670 return err 671 } 672 673 bind.WaitMined(context.Background(), c.client.conn, tx) 674 675 return nil 676 } 677 678 // Redeem supplies token to compound. 679 func (c *CompoundClient) Redeem(amount int64, coin coinType) error { 680 var ( 681 tx *types.Transaction 682 err error 683 ) 684 685 cTokenAddr, err := c.getPoolAddrFromCoin(coin) 686 if err != nil { 687 return err 688 } 689 690 opts := &bind.TransactOpts{ 691 From: c.client.opts.From, 692 Signer: c.client.opts.Signer, 693 GasLimit: 500000, 694 GasPrice: big.NewInt(20000000000), 695 } 696 697 switch coin { 698 case ETH: 699 cETHContract, err := ceth_binding.NewCETH(cTokenAddr, c.client.conn) 700 if err != nil { 701 return fmt.Errorf("Error getting cETH contract: %v", err) 702 } 703 704 tx, err = cETHContract.Redeem(opts, big.NewInt(amount)) 705 case BAT, COMP, DAI, REP, SAI, UNI, USDC, USDT, WBTC, ZRX: 706 cTokenContract, err := cToken.NewCToken(cTokenAddr, c.client.conn) 707 if err != nil { 708 return fmt.Errorf("Error getting cToken contract: %v", err) 709 } 710 711 tx, err = cTokenContract.Redeem(opts, big.NewInt(amount)) 712 } 713 714 if err != nil { 715 return err 716 } 717 718 bind.WaitMined(context.Background(), c.client.conn, tx) 719 720 return nil 721 } 722 723 // BalanceOf return the balance of given cToken. 724 func (c *CompoundClient) BalanceOf(coin coinType) (*big.Int, error) { 725 var ( 726 val *big.Int 727 err error 728 ) 729 730 cTokenAddr, err := c.getPoolAddrFromCoin(coin) 731 if err != nil { 732 return nil, err 733 } 734 735 switch coin { 736 case ETH: 737 cETHContract, err := ceth_binding.NewCETH(cTokenAddr, c.client.conn) 738 if err != nil { 739 return nil, fmt.Errorf("Error getting cETH contract") 740 } 741 742 val, err = cETHContract.BalanceOf(nil, c.client.opts.From) 743 case BAT, COMP, DAI, REP, SAI, UNI, USDC, USDT, WBTC, ZRX: 744 cTokenContract, err := cToken.NewCToken(cTokenAddr, c.client.conn) 745 if err != nil { 746 return nil, fmt.Errorf("Error getting cDai contract") 747 } 748 749 val, err = cTokenContract.BalanceOf(nil, c.client.opts.From) 750 default: 751 return nil, fmt.Errorf("Not support token in balanceOf: %v", coin) 752 } 753 754 if err != nil { 755 return big.NewInt(0), fmt.Errorf("Error getting balance of cToken: %v", err) 756 } 757 return val, nil 758 } 759 760 // BalanceOfUnderlying return the balance of given cToken 761 func (c *CompoundClient) BalanceOfUnderlying(coin coinType) (*types.Transaction, error) { 762 var ( 763 tx *types.Transaction 764 err error 765 ) 766 767 cTokenAddr, err := c.getPoolAddrFromCoin(coin) 768 if err != nil { 769 return nil, err 770 } 771 772 switch coin { 773 case ETH: 774 cETHContract, err := ceth_binding.NewCETH(cTokenAddr, c.client.conn) 775 if err != nil { 776 fmt.Printf("Error getting cETH contract") 777 } 778 779 tx, err = cETHContract.BalanceOfUnderlying(nil, c.client.opts.From) 780 case BAT, COMP, DAI, REP, SAI, UNI, USDC, USDT, WBTC, ZRX: 781 cTokenContract, err := cToken.NewCToken(cTokenAddr, c.client.conn) 782 if err != nil { 783 return nil, fmt.Errorf("Error getting cDai contract") 784 } 785 786 tx, err = cTokenContract.BalanceOfUnderlying(nil, c.client.opts.From) 787 } 788 789 if err != nil { 790 fmt.Printf("Error getting balance of cToken: %v", err) 791 return nil, err 792 } 793 return tx, nil 794 } 795 796 // SupplyActions create a supply action to supply asset to Compound. 797 func (c *CompoundClient) SupplyActions(size *big.Int, coin coinType) *Actions { 798 if coin == ETH { 799 return c.supplyActionsETH(size, coin) 800 } else { 801 return c.supplyActionsERC20(size, coin) 802 } 803 } 804 805 func (c *CompoundClient) supplyActionsETH(size *big.Int, coin coinType) *Actions { 806 parsed, err := abi.JSON(strings.NewReader(hcether.HcetherABI)) 807 if err != nil { 808 return nil 809 } 810 data, err := parsed.Pack("mint", size) 811 if err != nil { 812 return nil 813 } 814 return &Actions{ 815 Actions: []action{ 816 { 817 handlerAddr: common.HexToAddress(hCEtherAddr), 818 data: data, 819 ethersNeeded: size, 820 }, 821 }, 822 } 823 } 824 825 func (c *CompoundClient) supplyActionsERC20(size *big.Int, coin coinType) *Actions { 826 parsed, err := abi.JSON(strings.NewReader(hctoken.HctokenABI)) 827 if err != nil { 828 return nil 829 } 830 mintData, err := parsed.Pack("mint", CoinToCompoundMap[DAI], size) 831 if err != nil { 832 return nil 833 } 834 return &Actions{ 835 Actions: []action{ 836 { 837 handlerAddr: common.HexToAddress(hCTokenAddr), 838 data: mintData, 839 ethersNeeded: big.NewInt(0), 840 approvalTokens: []common.Address{CoinToAddressMap[coin]}, 841 approvalTokenAmounts: []*big.Int{size}, 842 }, 843 }, 844 } 845 } 846 847 // RedeemActions create a Compound redeem action to be executed. 848 func (c *CompoundClient) RedeemActions(size *big.Int, coin coinType) *Actions { 849 if coin == ETH { 850 return c.redeemActionsETH(size, coin) 851 } else { 852 return c.redeemActionsERC20(size, coin) 853 } 854 } 855 856 func (c *CompoundClient) redeemActionsETH(size *big.Int, coin coinType) *Actions { 857 parsed, err := abi.JSON(strings.NewReader(hcether.HcetherABI)) 858 if err != nil { 859 return nil 860 } 861 data, err := parsed.Pack("redeem", size) 862 if err != nil { 863 return nil 864 } 865 return &Actions{ 866 Actions: []action{ 867 { 868 handlerAddr: common.HexToAddress(hCEtherAddr), 869 data: data, 870 ethersNeeded: size, 871 }, 872 }, 873 } 874 } 875 876 func (c *CompoundClient) redeemActionsERC20(size *big.Int, coin coinType) *Actions { 877 parsed, err := abi.JSON(strings.NewReader(hctoken.HctokenABI)) 878 if err != nil { 879 return nil 880 } 881 redeemData, err := parsed.Pack("redeem", CoinToCompoundMap[coin], size) 882 if err != nil { 883 return nil 884 } 885 return &Actions{ 886 Actions: []action{ 887 { 888 handlerAddr: common.HexToAddress(hCTokenAddr), 889 data: redeemData, 890 ethersNeeded: big.NewInt(0), 891 approvalTokens: []common.Address{CoinToCompoundMap[coin]}, 892 approvalTokenAmounts: []*big.Int{size}, 893 }, 894 }, 895 } 896 } 897 898 // FlashLoanActions create an action to perform Uniswap flashloan. 899 func (c *AaveClient) FlashLoanActions(size *big.Int, coin coinType, actions *Actions) *Actions { 900 handlers := []common.Address{} 901 datas := make([][]byte, 0) 902 totalEthers := big.NewInt(0) 903 for i := 0; i < len(actions.Actions); i++ { 904 handlers = append(handlers, actions.Actions[i].handlerAddr) 905 datas = append(datas, actions.Actions[i].data) 906 totalEthers.Add(totalEthers, actions.Actions[i].ethersNeeded) 907 } 908 909 proxy, err := abi.JSON(strings.NewReader(furucombo.FurucomboABI)) 910 if err != nil { 911 return nil 912 } 913 payloadData, err := proxy.Pack("execs", handlers, datas) 914 if err != nil { 915 return nil 916 } 917 haave, err := abi.JSON(strings.NewReader(haave.HaaveABI)) 918 if err != nil { 919 return nil 920 } 921 // skip the first 4 bytes to omit the function selector 922 flashLoanData, err := haave.Pack("flashLoan", CoinToAddressMap[coin], size, payloadData[4:]) 923 return &Actions{ 924 Actions: []action{ 925 { 926 handlerAddr: common.HexToAddress(hAaveAddr), 927 data: flashLoanData, 928 ethersNeeded: totalEthers, 929 }, 930 }, 931 } 932 } 933 934 func (c *CompoundClient) getPoolAddrFromCoin(coin coinType) (common.Address, error) { 935 if val, ok := CoinToCompoundMap[coin]; ok { 936 return val, nil 937 } 938 return common.Address{}, fmt.Errorf("No corresponding compound pool for token: %v", coin) 939 } 940 941 // yearn----------------------------------------------------------------------------------------------------- 942 943 // YearnClient is an instance of Compound protocol. 944 type YearnClient struct { 945 client *DefiClient 946 tokenToVault map[common.Address]common.Address 947 } 948 949 // Yearn returns a Yearn client. 950 func (c *DefiClient) Yearn() *YearnClient { 951 yearnClient := new(YearnClient) 952 yearnClient.client = c 953 954 yregistry, err := yregistry.NewYregistry(common.HexToAddress(yRegistryAddr), c.conn) 955 if err != nil { 956 return nil 957 } 958 959 vaults, err := yregistry.GetVaults(nil) 960 if err != nil { 961 return nil 962 } 963 964 vaultInfos, err := yregistry.GetVaultsInfo(nil) 965 if err != nil { 966 return nil 967 } 968 969 yearnClient.tokenToVault = make(map[common.Address]common.Address) 970 for i := 0; i < len(vaults); i++ { 971 yearnClient.tokenToVault[vaultInfos.TokenArray[i]] = vaults[i] 972 } 973 974 return yearnClient 975 } 976 977 func (c *YearnClient) addLiquidity(size *big.Int, coin coinType) error { 978 var ( 979 tx *types.Transaction 980 err error 981 ) 982 opts := &bind.TransactOpts{ 983 From: c.client.opts.From, 984 Signer: c.client.opts.Signer, 985 GasLimit: 500000, 986 GasPrice: big.NewInt(20000000000), 987 } 988 989 if coin == ETH { 990 weth, err := yweth.NewYweth(common.HexToAddress(yETHVaultAddr), c.client.conn) 991 if err != nil { 992 return fmt.Errorf("Error getting weth contract") 993 } 994 opts.Value = size 995 tx, err = weth.DepositETH(opts) 996 } else if coin != ETH { 997 tokenAddr := CoinToAddressMap[coin] 998 vaultAddr, ok := c.tokenToVault[tokenAddr] 999 if !ok { 1000 return fmt.Errorf("No corresponding vault found for: %v ", coin) 1001 } 1002 err = Approve(c.client, coin, vaultAddr, size) 1003 yvault, err := yvault.NewYvault(vaultAddr, c.client.conn) 1004 if err != nil { 1005 return fmt.Errorf("Error getting weth contract") 1006 } 1007 opts.Value = size 1008 tx, err = yvault.Deposit(opts, size) 1009 } 1010 1011 if err != nil { 1012 fmt.Printf("Error deposit into vault: %v", err) 1013 return err 1014 } 1015 1016 bind.WaitMined(context.Background(), c.client.conn, tx) 1017 1018 return nil 1019 } 1020 1021 func (c *YearnClient) removeLiquidity(size *big.Int, coin coinType) error { 1022 var ( 1023 tx *types.Transaction 1024 err error 1025 ) 1026 opts := &bind.TransactOpts{ 1027 From: c.client.opts.From, 1028 Signer: c.client.opts.Signer, 1029 GasLimit: 500000, 1030 GasPrice: big.NewInt(20000000000), 1031 } 1032 1033 if coin == ETH { 1034 weth, err := yweth.NewYweth(common.HexToAddress(yETHVaultAddr), c.client.conn) 1035 if err != nil { 1036 return fmt.Errorf("Error getting weth contract") 1037 } 1038 tx, err = weth.WithdrawETH(opts, size) 1039 } else if coin != ETH { 1040 tokenAddr := CoinToAddressMap[coin] 1041 vaultAddr, ok := c.tokenToVault[tokenAddr] 1042 if !ok { 1043 return fmt.Errorf("No corresponding vault found for: %v ", coin) 1044 } 1045 yvault, err := yvault.NewYvault(vaultAddr, c.client.conn) 1046 if err != nil { 1047 return fmt.Errorf("Error getting weth contract") 1048 } 1049 opts.Value = size 1050 tx, err = yvault.Withdraw(opts, size) 1051 } 1052 1053 if err != nil { 1054 fmt.Printf("Error withdraw from vault: %v", err) 1055 return err 1056 } 1057 1058 bind.WaitMined(context.Background(), c.client.conn, tx) 1059 1060 return nil 1061 } 1062 1063 // AddLiquidityActions creates an add liquidity action to Yearn. 1064 func (c *YearnClient) AddLiquidityActions(size *big.Int, coin coinType) *Actions { 1065 if coin == ETH { 1066 return c.addLiquidityActionsETH(size, coin) 1067 } else { 1068 return c.addLiquidityActionsERC20(size, coin) 1069 } 1070 } 1071 1072 func (c *YearnClient) addLiquidityActionsETH(size *big.Int, coin coinType) *Actions { 1073 parsed, err := abi.JSON(strings.NewReader(hyearn.HyearnABI)) 1074 if err != nil { 1075 return nil 1076 } 1077 data, err := parsed.Pack("depositETH", size, common.HexToAddress(yETHVaultAddr)) 1078 if err != nil { 1079 return nil 1080 } 1081 return &Actions{ 1082 Actions: []action{ 1083 { 1084 handlerAddr: common.HexToAddress(hYearnAddr), 1085 data: data, 1086 ethersNeeded: size, 1087 }, 1088 }, 1089 } 1090 } 1091 1092 func (c *YearnClient) addLiquidityActionsERC20(size *big.Int, coin coinType) *Actions { 1093 parsed, err := abi.JSON(strings.NewReader(hyearn.HyearnABI)) 1094 if err != nil { 1095 return nil 1096 } 1097 tokenAddr := CoinToAddressMap[coin] 1098 vaultAddr, ok := c.tokenToVault[tokenAddr] 1099 if !ok { 1100 return nil 1101 } 1102 data, err := parsed.Pack("deposit", vaultAddr, size) 1103 if err != nil { 1104 return nil 1105 } 1106 return &Actions{ 1107 Actions: []action{ 1108 { 1109 handlerAddr: common.HexToAddress(hYearnAddr), 1110 data: data, 1111 ethersNeeded: big.NewInt(0), 1112 approvalTokens: []common.Address{CoinToAddressMap[coin]}, 1113 approvalTokenAmounts: []*big.Int{size}, 1114 }, 1115 }, 1116 } 1117 } 1118 1119 // RemoveLiquidityActions creates a remove liquidity action to Yearn. 1120 func (c *YearnClient) RemoveLiquidityActions(size *big.Int, coin coinType) *Actions { 1121 if coin == ETH { 1122 return c.removeLiquidityActionsETH(size, coin) 1123 } else { 1124 return c.removeLiquidityActionsERC20(size, coin) 1125 } 1126 } 1127 1128 func (c *YearnClient) removeLiquidityActionsETH(size *big.Int, coin coinType) *Actions { 1129 parsed, err := abi.JSON(strings.NewReader(hyearn.HyearnABI)) 1130 if err != nil { 1131 return nil 1132 } 1133 data, err := parsed.Pack("withdrawETH", common.HexToAddress(yETHVaultAddr), size) 1134 if err != nil { 1135 return nil 1136 } 1137 return &Actions{ 1138 Actions: []action{ 1139 { 1140 handlerAddr: common.HexToAddress(hYearnAddr), 1141 data: data, 1142 ethersNeeded: big.NewInt(0), 1143 approvalTokens: []common.Address{common.HexToAddress(yETHVaultAddr)}, 1144 approvalTokenAmounts: []*big.Int{size}, 1145 }, 1146 }, 1147 } 1148 } 1149 1150 func (c *YearnClient) removeLiquidityActionsERC20(size *big.Int, coin coinType) *Actions { 1151 parsed, err := abi.JSON(strings.NewReader(hyearn.HyearnABI)) 1152 if err != nil { 1153 return nil 1154 } 1155 data, err := parsed.Pack("withdraw", common.HexToAddress(yETHVaultAddr), size) 1156 if err != nil { 1157 return nil 1158 } 1159 return &Actions{ 1160 Actions: []action{ 1161 { 1162 handlerAddr: common.HexToAddress(hYearnAddr), 1163 data: data, 1164 ethersNeeded: big.NewInt(0), 1165 }, 1166 }, 1167 } 1168 } 1169 1170 // Aave---------------------------------------------------------------------------- 1171 1172 // AaveClient is an instance of Aave protocol. 1173 type AaveClient struct { 1174 client *DefiClient 1175 lendingPool *lendingpool.Lendingpool 1176 } 1177 1178 // Aave returns an Aave client which contains functions that you can use to interact with Aave. 1179 func (c *DefiClient) Aave() *AaveClient { 1180 aaveClient := new(AaveClient) 1181 aaveClient.client = c 1182 1183 lendingpool, err := lendingpool.NewLendingpool(common.HexToAddress(aaveLendingPoolAddr), c.conn) 1184 if err != nil { 1185 return nil 1186 } 1187 aaveClient.lendingPool = lendingpool 1188 return aaveClient 1189 } 1190 1191 // Lend lend to the Aave lending pool. 1192 func (c *AaveClient) Lend(size *big.Int, coin coinType) error { 1193 opts := &bind.TransactOpts{ 1194 From: c.client.opts.From, 1195 Signer: c.client.opts.Signer, 1196 GasLimit: 500000, 1197 GasPrice: big.NewInt(20000000000), 1198 } 1199 1200 if coin != ETH { 1201 Approve(c.client, coin, common.HexToAddress(aaveLendingPoolCoreAddr), size) 1202 } 1203 1204 tx, err := c.lendingPool.Deposit(opts, CoinToAddressMap[coin], size, 0) 1205 if err != nil { 1206 return err 1207 } 1208 bind.WaitMined(context.Background(), c.client.conn, tx) 1209 return nil 1210 } 1211 1212 // Borrow borrow money from lending pool. 1213 func (c *AaveClient) Borrow(size *big.Int, coin coinType, interestRate rateModel) error { 1214 return nil 1215 } 1216 1217 // ReserveData is a struct described the status of Aave lending pool. 1218 type ReserveData struct { 1219 CurrentATokenBalance *big.Int 1220 CurrentBorrowBalance *big.Int 1221 PrincipalBorrowBalance *big.Int 1222 BorrowRateMode *big.Int 1223 BorrowRate *big.Int 1224 LiquidityRate *big.Int 1225 OriginationFee *big.Int 1226 VariableBorrowIndex *big.Int 1227 LastUpdateTimestamp *big.Int 1228 UsageAsCollateralEnabled bool 1229 } 1230 1231 // GetUserReserveData get the reserve data. 1232 func (c *AaveClient) GetUserReserveData(addr common.Address, user common.Address) (ReserveData, error) { 1233 data, err := c.lendingPool.GetUserReserveData(nil, addr, user) 1234 if err != nil { 1235 return ReserveData{}, err 1236 } 1237 return data, nil 1238 } 1239 1240 // Kyberswap---------------------------------------------------------------------- 1241 1242 // KyberswapClient struct 1243 type KyberswapClient struct { 1244 client *DefiClient 1245 } 1246 1247 // Kyberswap returns a Kyberswap client. 1248 func (c *DefiClient) Kyberswap() *KyberswapClient { 1249 kyberClient := new(KyberswapClient) 1250 kyberClient.client = c 1251 return kyberClient 1252 } 1253 1254 // SwapActions creates a swap action. 1255 func (c *KyberswapClient) SwapActions(size *big.Int, baseCurrency coinType, quoteCurrency coinType) *Actions { 1256 var ( 1257 data []byte 1258 err error 1259 ethersNeeded *big.Int = big.NewInt(0) 1260 ) 1261 1262 parsed, err := abi.JSON(strings.NewReader(hkyber.HkyberABI)) 1263 if err != nil { 1264 return nil 1265 } 1266 1267 if quoteCurrency == ETH { 1268 ethersNeeded = size 1269 data, err = parsed.Pack("swapEtherToToken", size, CoinToAddressMap[baseCurrency], big.NewInt(0)) 1270 } else { 1271 if baseCurrency == ETH { 1272 data, err = parsed.Pack("swapTokenToEther", CoinToAddressMap[baseCurrency], size, big.NewInt(0)) 1273 } else { 1274 data, err = parsed.Pack("swapTokenToToken", CoinToAddressMap[baseCurrency], size, CoinToAddressMap[quoteCurrency], big.NewInt(0)) 1275 } 1276 } 1277 1278 if err != nil { 1279 return nil 1280 } 1281 1282 return &Actions{ 1283 Actions: []action{ 1284 { 1285 handlerAddr: common.HexToAddress(hKyberAddr), 1286 data: data, 1287 ethersNeeded: ethersNeeded, 1288 }, 1289 }, 1290 } 1291 1292 } 1293 1294 // Sushiswap---------------------------------------------------------------------- 1295 1296 // SushiswapClient struct 1297 type SushiswapClient struct { 1298 client *DefiClient 1299 } 1300 1301 // Sushiswap returns a Sushiswap client. 1302 func (c *DefiClient) Sushiswap() *SushiswapClient { 1303 sushiswapClient := new(SushiswapClient) 1304 sushiswapClient.client = c 1305 return sushiswapClient 1306 } 1307 1308 // SwapActions create a new swap action. 1309 func (c *SushiswapClient) SwapActions(size *big.Int, baseCurrency coinType, quoteCurrency coinType) *Actions { 1310 var callData []byte 1311 var ethersNeeded = big.NewInt(0) 1312 var approvalTokens []common.Address = nil 1313 var approvalTokenAmounts []*big.Int = nil 1314 1315 if quoteCurrency == ETH { 1316 ethersNeeded = size 1317 callData = swapETHToTokenData(size, baseCurrency) 1318 } else { 1319 if baseCurrency == ETH { 1320 approvalTokens = []common.Address{CoinToAddressMap[quoteCurrency]} 1321 approvalTokenAmounts = []*big.Int{size} 1322 callData = swapTokenToETHData(size, quoteCurrency) 1323 } else { 1324 approvalTokens = []common.Address{CoinToAddressMap[quoteCurrency]} 1325 approvalTokenAmounts = []*big.Int{size} 1326 callData = swapTokenToTokenData(size, baseCurrency, quoteCurrency) 1327 } 1328 } 1329 1330 return &Actions{ 1331 Actions: []action{ 1332 { 1333 handlerAddr: common.HexToAddress(hSushiswapAddr), 1334 data: callData, 1335 ethersNeeded: ethersNeeded, 1336 approvalTokens: approvalTokens, 1337 approvalTokenAmounts: approvalTokenAmounts, 1338 }, 1339 }, 1340 } 1341 } 1342 1343 // Curve------------------------------------------------------------------------- 1344 1345 // CurveClient struct 1346 type CurveClient struct { 1347 client *DefiClient 1348 } 1349 1350 // Curve returns a Curve client. 1351 func (c *DefiClient) Curve() *CurveClient { 1352 curveClient := new(CurveClient) 1353 curveClient.client = c 1354 return curveClient 1355 } 1356 1357 // ExchangeActions creates a Curve exchange action to swap from one stable coin to another. 1358 func (c *CurveClient) ExchangeActions( 1359 handler common.Address, token1Addr common.Address, token2Addr common.Address, 1360 i *big.Int, j *big.Int, dx *big.Int, minDy *big.Int) *Actions { 1361 1362 parsed, err := abi.JSON(strings.NewReader(hcurve.HcurveABI)) 1363 if err != nil { 1364 return nil 1365 } 1366 1367 data, err := parsed.Pack("exchange", handler, token1Addr, token2Addr, i, j, dx, minDy) 1368 1369 if err != nil { 1370 return nil 1371 } 1372 return &Actions{ 1373 Actions: []action{ 1374 { 1375 handlerAddr: common.HexToAddress(hCurveAddr), 1376 data: data, 1377 ethersNeeded: big.NewInt(0), 1378 approvalTokens: []common.Address{token1Addr}, 1379 approvalTokenAmounts: []*big.Int{dx}, 1380 }, 1381 }, 1382 } 1383 } 1384 1385 // ExchangeUnderlyingActions creates a Curve exchangeUnderlying action. 1386 // `handler` is the address of the Curve pool. 1387 // `token1Addr` is the address of the input token. 1388 // `token2Addr` is the address of the output token. 1389 // `i` is the index of the input token in the pool. 1390 // `j` is the index of the output token in the pool. 1391 // `dx` is the amount of the input token that you want to swap 1392 // `minDy` is the minimum amount of the output token that you want to receive. 1393 func (c *CurveClient) ExchangeUnderlyingActions(handler common.Address, token1Addr common.Address, token2Addr common.Address, i *big.Int, j *big.Int, dx *big.Int, minDy *big.Int) *Actions { 1394 parsed, err := abi.JSON(strings.NewReader(hcurve.HcurveABI)) 1395 if err != nil { 1396 return nil 1397 } 1398 1399 data, err := parsed.Pack("exchangeUnderlying", handler, token1Addr, token2Addr, i, j, dx, minDy) 1400 if err != nil { 1401 return nil 1402 } 1403 1404 return &Actions{ 1405 Actions: []action{ 1406 { 1407 handlerAddr: common.HexToAddress(hCurveAddr), 1408 data: data, 1409 ethersNeeded: big.NewInt(0), 1410 approvalTokens: []common.Address{token1Addr}, 1411 approvalTokenAmounts: []*big.Int{dx}, 1412 }, 1413 }, 1414 } 1415 } 1416 1417 // AddLiquidityActions adds liqudity to the given pool. 1418 // `handler` is the address of the Curve pool. 1419 // `pool` is the address of the pool token, e.g. bCRV token or 3CRV token. 1420 // `tokens` is the addresses of the tokens that is in the pool. 1421 // `amounts` is how much amount of each tokens you want to deposit. 1422 // `minAmount` is the minimum amount of pool token that you want to get back as a result. 1423 func (c *CurveClient) AddLiquidityActions( 1424 handler common.Address, pool common.Address, tokens []common.Address, 1425 amounts []*big.Int, minAmount *big.Int) *Actions { 1426 1427 parsed, err := abi.JSON(strings.NewReader(hcurve.HcurveABI)) 1428 if err != nil { 1429 return nil 1430 } 1431 1432 data, err := parsed.Pack("addLiquidity", handler, pool, tokens, amounts, minAmount) 1433 1434 if err != nil { 1435 return nil 1436 } 1437 return &Actions{ 1438 Actions: []action{ 1439 { 1440 handlerAddr: common.HexToAddress(hCurveAddr), 1441 data: data, 1442 ethersNeeded: big.NewInt(0), 1443 approvalTokens: tokens, 1444 approvalTokenAmounts: amounts, 1445 }, 1446 }, 1447 } 1448 } 1449 1450 // RemoveLiquidityActions creates remove liquidity action on Curve. 1451 // `handler` is the address of the Curve pool. 1452 // `pool` is the address of the pool token, e.g. bCRV token or 3CRV token. 1453 // `tokenI` is the addresse of the tokens that you want to remove. 1454 // `tokenAmount` is how much amount of token you want to deposit. 1455 // `i` is the index of the token in the given pool. 1456 // `minAmount` is the minimum amount of the underlying token that you want to get back as a result. 1457 func (c *CurveClient) RemoveLiquidityActions( 1458 handler common.Address, pool common.Address, tokenI common.Address, tokenAmount *big.Int, i *big.Int, minAmount *big.Int, 1459 ) *Actions { 1460 parsed, err := abi.JSON(strings.NewReader(hcurve.HcurveABI)) 1461 if err != nil { 1462 return nil 1463 } 1464 1465 data, err := parsed.Pack("removeLiquidityOneCoin", handler, pool, tokenI, tokenAmount, i, minAmount) 1466 1467 if err != nil { 1468 return nil 1469 } 1470 return &Actions{ 1471 Actions: []action{ 1472 { 1473 handlerAddr: common.HexToAddress(hCurveAddr), 1474 data: data, 1475 ethersNeeded: big.NewInt(0), 1476 approvalTokens: []common.Address{pool}, 1477 approvalTokenAmounts: []*big.Int{tokenAmount}, 1478 }, 1479 }, 1480 } 1481 } 1482 1483 // Maker------------------------------------------------------------------------ 1484 1485 // MakerClient is an instance of Maker protocol. 1486 type MakerClient struct { 1487 client *DefiClient 1488 } 1489 1490 // Maker creates a new instance of MakerClient 1491 func (c *DefiClient) Maker() *MakerClient { 1492 makerClient := new(MakerClient) 1493 makerClient.client = c 1494 return makerClient 1495 } 1496 1497 // GenerateDaiAction generate an action to create a vault and get some DAI 1498 func (c *MakerClient) GenerateDaiAction(collateralAmount *big.Int, daiAmount *big.Int, collateralType coinType) *Actions { 1499 if collateralType == ETH { 1500 return c.generateDaiActionETH(collateralAmount, daiAmount) 1501 } else { 1502 return c.generateDaiActionErc20(collateralAmount, daiAmount, collateralType) 1503 } 1504 } 1505 1506 func (c *MakerClient) generateDaiActionETH(collateralAmount *big.Int, daiAmount *big.Int) *Actions { 1507 1508 parsed, err := abi.JSON(strings.NewReader(hmaker.HmakerABI)) 1509 if err != nil { 1510 return nil 1511 } 1512 1513 data, err := parsed.Pack("openLockETHAndDraw", collateralAmount, CoinToJoinMap[ETH], CoinToJoinMap[DAI], CoinToIlkMap[ETH], daiAmount) 1514 1515 if err != nil { 1516 return nil 1517 } 1518 return &Actions{ 1519 Actions: []action{ 1520 { 1521 handlerAddr: common.HexToAddress(hMakerDaoAddr), 1522 data: data, 1523 ethersNeeded: collateralAmount, 1524 }, 1525 }, 1526 } 1527 } 1528 1529 func (c *MakerClient) generateDaiActionErc20(collateralAmount *big.Int, daiAmount *big.Int, collateralType coinType) *Actions { 1530 parsed, err := abi.JSON(strings.NewReader(hmaker.HmakerABI)) 1531 if err != nil { 1532 return nil 1533 } 1534 1535 data, err := parsed.Pack("openLockGemAndDraw", CoinToJoinMap[collateralType], CoinToJoinMap[DAI], CoinToIlkMap[collateralType], collateralAmount, daiAmount) 1536 1537 if err != nil { 1538 return nil 1539 } 1540 return &Actions{ 1541 Actions: []action{ 1542 { 1543 handlerAddr: common.HexToAddress(hMakerDaoAddr), 1544 data: data, 1545 ethersNeeded: big.NewInt(0), 1546 approvalTokens: []common.Address{CoinToAddressMap[collateralType]}, 1547 approvalTokenAmounts: []*big.Int{collateralAmount}, 1548 }, 1549 }, 1550 } 1551 } 1552 1553 // DepositCollateralActions deposits additional collateral to the given vault. 1554 func (c *MakerClient) DepositCollateralActions(collateralAmount *big.Int, collateralType coinType, cdp *big.Int) *Actions { 1555 if collateralType == ETH { 1556 return c.depositETHActions(collateralAmount, collateralType, cdp) 1557 } else { 1558 return c.depositERC20Actions(collateralAmount, collateralType, cdp) 1559 } 1560 } 1561 1562 func (c *MakerClient) depositETHActions(collateralAmount *big.Int, collateralType coinType, cdp *big.Int) *Actions { 1563 parsed, err := abi.JSON(strings.NewReader(hmaker.HmakerABI)) 1564 if err != nil { 1565 return nil 1566 } 1567 1568 data, err := parsed.Pack("safeLockETH", collateralAmount, CoinToJoinMap[ETH], cdp) 1569 1570 if err != nil { 1571 return nil 1572 } 1573 return &Actions{ 1574 Actions: []action{ 1575 { 1576 handlerAddr: common.HexToAddress(hMakerDaoAddr), 1577 data: data, 1578 ethersNeeded: collateralAmount, 1579 }, 1580 }, 1581 } 1582 } 1583 1584 func (c *MakerClient) depositERC20Actions(collateralAmount *big.Int, collateralType coinType, cdp *big.Int) *Actions { 1585 parsed, err := abi.JSON(strings.NewReader(hmaker.HmakerABI)) 1586 if err != nil { 1587 return nil 1588 } 1589 1590 data, err := parsed.Pack("safeLockGem", CoinToJoinMap[collateralType], cdp, collateralAmount) 1591 1592 if err != nil { 1593 return nil 1594 } 1595 return &Actions{ 1596 Actions: []action{ 1597 { 1598 handlerAddr: common.HexToAddress(hMakerDaoAddr), 1599 data: data, 1600 ethersNeeded: big.NewInt(0), 1601 approvalTokens: []common.Address{CoinToAddressMap[collateralType]}, 1602 approvalTokenAmounts: []*big.Int{collateralAmount}, 1603 }, 1604 }, 1605 } 1606 } 1607 1608 // WipeAction creates a wipe action to decrease debt for th given cdp/vault. 1609 func (c *MakerClient) WipeAction(daiAmount *big.Int, cdp *big.Int) *Actions { 1610 parsed, err := abi.JSON(strings.NewReader(hmaker.HmakerABI)) 1611 if err != nil { 1612 return nil 1613 } 1614 1615 data, err := parsed.Pack("wipe", CoinToJoinMap[DAI], cdp, daiAmount) 1616 1617 if err != nil { 1618 return nil 1619 } 1620 return &Actions{ 1621 Actions: []action{ 1622 { 1623 handlerAddr: common.HexToAddress(hMakerDaoAddr), 1624 data: data, 1625 ethersNeeded: big.NewInt(0), 1626 }, 1627 }, 1628 } 1629 } 1630 1631 // Balancer----------------------------------------------------------- 1632 1633 // BalancerClient is an instance of Balancer protocol. 1634 type BalancerClient struct { 1635 client *DefiClient 1636 } 1637 1638 // Balancer creates a new instance of BalancerClient 1639 func (c *DefiClient) Balancer() *BalancerClient { 1640 balancerClient := new(BalancerClient) 1641 balancerClient.client = c 1642 return balancerClient 1643 } 1644 1645 // Swap swaps on Balancer Exchange 1646 func (c *BalancerClient) Swap(inputCoin coinType, outputCoin coinType, inputAmount *big.Int) *Actions { 1647 parsed, err := abi.JSON(strings.NewReader(hbalancer_exchange.HbalancerExchangeABI)) 1648 if err != nil { 1649 return nil 1650 } 1651 1652 data, err := parsed.Pack("smartSwapExactIn", CoinToAddressMap[inputCoin], CoinToAddressMap[outputCoin], inputAmount, big.NewInt(0), big.NewInt(10)) 1653 1654 if err != nil { 1655 return nil 1656 } 1657 1658 if inputCoin == ETH { 1659 return &Actions{ 1660 Actions: []action{ 1661 { 1662 handlerAddr: common.HexToAddress(hBalancerExchangeAddr), 1663 data: data, 1664 ethersNeeded: inputAmount, 1665 }, 1666 }, 1667 } 1668 } else { 1669 return &Actions{ 1670 Actions: []action{ 1671 { 1672 handlerAddr: common.HexToAddress(hBalancerExchangeAddr), 1673 data: data, 1674 ethersNeeded: big.NewInt(0), 1675 approvalTokens: []common.Address{CoinToAddressMap[inputCoin]}, 1676 approvalTokenAmounts: []*big.Int{inputAmount}, 1677 }, 1678 }, 1679 } 1680 } 1681 1682 } 1683 1684 // utility------------------------------------------------------------------------ 1685 1686 // Approve approves ERC-20 token transfer. 1687 func Approve(client *DefiClient, coin coinType, addr common.Address, size *big.Int) error { 1688 erc20Contract, err := erc20.NewErc20(CoinToAddressMap[coin], client.conn) 1689 if err != nil { 1690 return err 1691 } 1692 opts := &bind.TransactOpts{ 1693 Signer: client.opts.Signer, 1694 From: client.opts.From, 1695 GasLimit: 500000, 1696 GasPrice: big.NewInt(20000000000), 1697 } 1698 tx, err := erc20Contract.Approve(opts, addr, size) 1699 bind.WaitMined(context.Background(), client.conn, tx) 1700 return nil 1701 } 1702 1703 // Convert string to a fixed length 32 byte. 1704 func byte32PutString(s string) [32]byte { 1705 var res [32]byte 1706 decoded, err := hex.DecodeString(s) 1707 if err != nil { 1708 return res 1709 } 1710 if len(s) > 32 { 1711 copy(res[:], decoded) 1712 } else { 1713 copy(res[32-len(s):], decoded) 1714 } 1715 return res 1716 }