{"id":17176198,"url":"https://github.com/haydenshively/nantucket","last_synced_at":"2025-03-17T14:17:37.395Z","repository":{"id":40704400,"uuid":"269178609","full_name":"haydenshively/Nantucket","owner":"haydenshively","description":"Flash loan liquidation bot for compound.finance","archived":false,"fork":false,"pushed_at":"2023-01-24T05:11:09.000Z","size":3837,"stargazers_count":191,"open_issues_count":32,"forks_count":53,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-03-03T23:51:18.162Z","etag":null,"topics":["bot","compound","finance","flash-loans","liquidation","smart-contract","web3js"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/haydenshively.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-03T19:40:42.000Z","updated_at":"2025-02-22T15:17:54.000Z","dependencies_parsed_at":"2023-01-28T16:31:09.267Z","dependency_job_id":null,"html_url":"https://github.com/haydenshively/Nantucket","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haydenshively%2FNantucket","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haydenshively%2FNantucket/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haydenshively%2FNantucket/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haydenshively%2FNantucket/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/haydenshively","download_url":"https://codeload.github.com/haydenshively/Nantucket/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244047646,"owners_count":20389206,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bot","compound","finance","flash-loans","liquidation","smart-contract","web3js"],"created_at":"2024-10-14T23:59:31.937Z","updated_at":"2025-03-17T14:17:37.358Z","avatar_url":"https://github.com/haydenshively.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nantucket\n\n![Node.js CI](https://github.com/haydenshively/nantucket/workflows/Node.js%20CI/badge.svg)\n\n----\n\n**Update, 7/26/21: I've open sourced Nantucket's successor [here](https://github.com/haydenshively/New-Bedford).**\n\n----\n\nNantucket is a (massively) upgraded version of my [Compound Liquidation Bot](https://github.com/haydenshively/Compound-Liquidation-Bot).\nFor capital-free liquidations, it was more or less state-of-the-art in November 2020.\nThat said, this space moves quickly, and there are obviously improvements to be made\n-- I'm not gonna leak my most recent alpha.\n\n## Features\n\n### Solidity\n\n- 🦄 Liquidate via Uniswap (v2) flash swaps\n- 👻 Liquidate via AAVE (v1) flash loans - you'll have to go back in git history for this\n- 🔢 Liquidate multiple accounts at once\n- 🧮 Compute repay amounts atomically on-chain\n- ⛽️ Burn CHI to reduce gas costs\n- 🏷 Atomically post prices to Compound's Open Price Feed\n- (optional) Contract to split profits between 2 people\n\n### Javascript\n\n- Fetch accounts from Compound's API every `N` minutes\n  - Compute revenue (in ETH) that can be expected from liquidating each user\n  - Store expected revenue, account health, and [repay, seize] token pair in Postgres database\n- Filter database accounts by `minRevenue` or `maxHealth`. Track these accounts\n  - Fetch on-chain balances every block (don't try to do this for more than ~500 accounts)\n  - Check liquidatability based on most recent Coinbase prices for each asset\n    - If account has supply, use *min* price seen since last on-chain posting\n    - Otherwise, use *max* price seen since last on-chain posting\n  - Pass liquidatable accounts to a transaction manager\n- Manage multiple nonces and/or automatic blind PGA bidding\n- Separate processes for these broad tasks\n  - Can have multiple account-liquidatability-checkers and multiple transaction managers\n- Extensive logging with Winston; optional Slack bot integration\n- Some (mostly broken) tests\n\n## Introduction\n\n\u003e Looking back on it, the rest of this README is pretty poorly scoped and/or outdated. You're probably better off\n\u003e learning elsewhere, and coming back here for the code.\n\nIf you're planning to use this code, you should know this stuff already. But if\nyou're a casual observer of my Github profile, feel free to read on.\n\nCompound is both a company and a collection of code (a decentralized app or \"Dapp\") that's stored on the Ethereum blockchain. The Dapp allows users to supply and\nborrow crypto tokens (e.g. WBTC, USDC, DAI, BAT). Suppliers earn interest, while borrowers pay interest.\n\nBut this system doesn't work like a regular bank -- there's no way to identify\nindividuals on the blockchain, so there's no way of knowing their creditworthiness.\nAs such, in order to borrow anything, users must first put up **collateral that\nexceeds the value of their desired loan** (if we get more technical, each crypto\ntoken has a \"collateral factor\" that indicates the % of collateral that a user can\nborrow). For example, suppose Bob believes that Bitcoin's price will fall soon.\nBob can supply USDC to Compound, borrow an amount of Bitcoin worth less than\n`collateralFactor * valueOfSuppliedUSDC`, and trade that borrowed Bitcoin for more\nUSDC. If Bob's belief comes true, he'll be able to re-trade the USDC for Bitcoin and\npay off his loan with some USDC left over.\n\nIf, on the other hand, Bob is wrong -- the price of Bitcoin rises -- then Bob is in\ntrouble. In this situation, the value of his borrowed Bitcoin may grow to exceed\nthe `collateralFactor * valueOfSuppliedUSDC`. If Bob fails to pay off his loan\nbefore this happens, then Bob is subject to liquidation.\n\nFor more introductory information, see [Compound's website](https://compound.finance) and for a deep dive into transaction dynamics read [this paper](https://arxiv.org/pdf/1904.05234.pdf).\n\n## Liquidation\n\n```js\nlet collatValue = 0.0;\nlet borrowValue = 0.0;\n\nfor (let cryptoToken of user.cryptoTokens) {\n  // Note that each crypto token can have a unique collateral factor\n  collatValue += user.walletSize[cryptoToken] * cryptoToken.priceInUSDollars * cryptoToken.collateralFactor;\n  borrowValue += user.loanSize[cryptoToken] * cryptoToken.priceInUSDollars;\n}\n\nconst userIsLiquidatable = borrowValue \u003e collatValue;\n```\n\nThe pseudocode above shows how Compound determines if a user is liquidatable or\nnot. If they are liquidatable, the next question is \"By how much?\" The number\nthat governs this is called the \"close factor,\" and so far has been constant at\n50%. This means that `liquidatableAmount \u003c= borrowValue * 0.50`, but it's not\nthe only constraint...\n\nIf successful, liquidators receive a portion of the user's collateral: `revenue = liquidatableAmount * liquidationIncentive`,\nwhere the liquidation incentive is usually around 110%. In order for this to work,\nthe user must actually have that much collateral available for the taking. This\nmeans that `liquidatableAmount \u003c= collatValue * liquidationIncentive`.\n\nBoth constraints must be satisfied for the liquidation to be successful. There are\nother things to consider as well, such as \"Which loan should I pay off?\" (if the\nuser has borrowed multiple types of crypto tokens) and \"Which collateral should I\nseize?\" (if the user has supplied multiple types of crypto tokens). To complicate\nthings further, v2 cTokens can be both repaid and seized in a single liquidation,\nbut normally `repayTokenType != seizeTokenType`.\n\nYou can find most of this liquidation logic [here](./src/database/tableusers.js) and [here](./src/messaging/candidate.js).\n\n## Flash Loans\n\n\u003e This section describes AAVE flash loans. Nantucket now uses Uniswap flash swaps, which work somewhat differently (and are more gas efficient in most cases)\n\nA flash loan is an atomic interaction (a single transaction on the blockchain) that\n(1) takes out a loan and (2) pays it off. Only certain Dapps allow this (e.g. AAVE,\nUniswapV2, and DyDx). What's great is that you can take out a loan of any size\nwithout first putting up collateral. If you fail to pay off your debt by the\nend of the transaction, the provider's software (AAVE, etc.) simply throws an error\nand the whole transaction is undone. The only penalty is the transaction fee (gas * gasPrice).\n\nNantucket uses AAVE flash loans to liquidate users on Compound:\n1. Borrow X tokens of type A from AAVE\n2. Liquidate user on Compound by paying off their debt with X tokens of type A\n3. As a reward, receive Y tokens of type B from Compound, seized from the user's collateral (where `Y = X * liquidationIncentive`). Note that type A and B can be\nthe same for DAI and USDT, but must be different otherwise\n4. Trade Y tokens of type B for Z tokens of type A on Uniswap. Assuming Uniswap's exchange rates aren't whack, Z should be greater than X.\n5. Repay AAVE loan using X tokens of type A. Technically AAVE also expects a small fee (0.0009%).\n6. Keep `Z - X` tokens of type A as profit.\n\nThis logic can be found in the [contracts folder](./contracts). I've already deployed the Solidity code to the blockchain, so it's now accessible via\n[wrappers](./src/network/webthree).\n\n## Pipeline\n\n\u003e This section is also somewhat outdated. In lieu of describing the current state of affairs, I think it's reasonable to expect potential users to read the code. **If you don't understand it, don't use it!!!**\n\nCompound (the company) provides an HTTP endpoint that returns information about all\nusers (address, supply amounts, and borrow amounts). They provide another HTTP\nendpoint that returns information about all tokens (address, real-world price,\ncollateral factor). Nantucket periodically polls this information, does some\ncomputation, and stores it in a Postgres database. The \"computation\" is really just\nto answer the questions \"Which type of token should I repay and seize if this\nuser becomes liquidatable?\" and \"How profitable would this be?\"\n\nA separate process periodically polls the database to get a subset of Compound\nusers. This subset is configurable via the arguments passed to the `Main`\nconstructor. Whenever a new block gets added to the Ethereum blockchain (~ every 15\nseconds), Nantucket loops through the users to decide (1) if they are\nliquidatable according to the Compound Dapp and (2) if they are liquidatable\naccording to token prices on Coinbase.\n\nIn case 1, the code immediately sends a transaction to liquidate them. The gas price\nof that transaction is\n`gasPriceRecommendedByGethBlockchainClient * someMultiplier` where `someMultiplier`\nis configurable in `Main`'s constructor. For example, if a `Main` instance is\nconfigured to look only at users where the potential profit is \u003e$10000, the gas\nprice multiplier may be set higher so that the transaction goes through faster --\nafter all, there's lots of competition for these high value liquidations.\n\nIn case 2, the user is added to the \"price wave\" list. A transaction is sent with\nthe intent that it will remain \"pending\" for a while. This is done by sending it\nwith a relatively low gas price (high enough that miners keep it in the transaction\npool, but low enough that it takes a while to be included in a block).\n\nNantucket also watches for the signature of Compound's price update transactions.\nThis is when Compound (the company) updates the Dapp's knowledge of token prices\nto match those of the real world (on Coinbase, for example). As soon as one of these\nprice update transactions is pending, Nantucket raises the gas price of pending\ntransactions to match the gas price of the price update transaction. In theory, this\nshould make the transactions happen consecutively, guaranteeing that we win the\nfirst-come first-serve liquidation battle. **In practice, other liquidators manage\nto put themselves in that position more reliably, and Nantucket loses.**\n\nKey Files:\n- [Start](./src/start.js)\n- [Worker](./src/worker.js)\n- [TxManager](./src/network/webthree/txmanager.js)\n\n## Usage and Disclaimer\n\nDon't. You will almost certainly loose money. Feel free to admire the code or use it as\na reference point, but please don't try to run it as-is.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaydenshively%2Fnantucket","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhaydenshively%2Fnantucket","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaydenshively%2Fnantucket/lists"}