Skip to content

Commit ccc40fd

Browse files
committed
Update README and add LICENSE
1 parent b892d86 commit ccc40fd

3 files changed

Lines changed: 136 additions & 36 deletions

File tree

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Hayden Shively
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 115 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,65 @@
22

33
![Node.js CI](https://github.com/haydenshively/nantucket/workflows/Node.js%20CI/badge.svg)
44

5+
Nantucket is a (massively) upgraded version of my [Compound Liquidation Bot](https://github.com/haydenshively/Compound-Liquidation-Bot).
6+
For capital-free liquidations, it was more or less state-of-the-art in November 2020.
7+
That said, this space moves quickly, and there are obviously improvements to be made
8+
-- I'm not gonna leak my most recent alpha.
9+
10+
## Features
11+
12+
### Solidity
13+
14+
- 🦄 Liquidate via Uniswap (v2) flash swaps
15+
- 👻 Liquidate via AAVE (v1) flash loans - you'll have to go back in git history for this
16+
- 🔢 Liquidate multiple accounts at once
17+
- 🧮 Compute repay amounts atomically on-chain (guaranteed accuracy)
18+
- ⛽️ Burn CHI to reduce gas costs
19+
- 🏷 Atomically post prices to Compound's Open Price Feed
20+
- (optional) Contract to split profits between 2 people
21+
22+
### Javascript
23+
24+
- Fetch accounts from Compound's API every `N` minutes
25+
- Compute revenue (in ETH) that can be expected from liquidating each user
26+
- Store expected revenue, account health, and [repay, seize] token pair in Postgres database
27+
- Filter database accounts by `minRevenue` or `maxHealth`. Track these accounts
28+
- Fetch on-chain balances every block (don't try to do this for more than ~500 accounts)
29+
- Check liquidatability based on most recent Coinbase prices for each asset
30+
- If account has supply, use *min* price seen since last on-chain posting
31+
- Otherwise, use *max* price seen since last on-chain posting
32+
- Pass liquidatable accounts to a transaction manager
33+
- Manage multiple nonces and/or automatic blind PGA bidding
34+
- Separate processes for these broad tasks
35+
- Can have multiple account-liquidatability-checkers and multiple transaction managers
36+
- Extensive logging with Winston; optional Slack bot integration
37+
- Some (mostly broken) tests
38+
539
## Introduction
640

41+
If you're planning to use this code, you should know this stuff already. But if
42+
you're a casual observer of my Github profile, feel free to read on.
43+
744
Compound 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
845
borrow crypto tokens (e.g. WBTC, USDC, DAI, BAT). Suppliers earn interest, while borrowers pay interest.
946

10-
But this system doesn't work like a regular bank -- there's no way to identify individuals on the blockchain, so there's no way of knowing their creditworthiness. As such, in order to borrow anything, users must first put up **collateral that exceeds the value of their desired loan** (if we get more technical, each crypto
11-
token has a "collateral factor" that indicates the % of collateral that a user can borrow). For example, suppose Bob believes that Bitcoin's price will fall soon.
12-
Bob can supply USDC to Compound, borrow an amount of Bitcoin worth less than `collateralFactor * valueOfSuppliedUSDC`, and trade that borrowed Bitcoin for more
13-
USDC. If Bob's belief comes true, he'll be able to re-trade the USDC for Bitcoin and pay off his loan with some USDC left over.
47+
But this system doesn't work like a regular bank -- there's no way to identify
48+
individuals on the blockchain, so there's no way of knowing their creditworthiness.
49+
As such, in order to borrow anything, users must first put up **collateral that
50+
exceeds the value of their desired loan** (if we get more technical, each crypto
51+
token has a "collateral factor" that indicates the % of collateral that a user can
52+
borrow). For example, suppose Bob believes that Bitcoin's price will fall soon.
53+
Bob can supply USDC to Compound, borrow an amount of Bitcoin worth less than
54+
`collateralFactor * valueOfSuppliedUSDC`, and trade that borrowed Bitcoin for more
55+
USDC. If Bob's belief comes true, he'll be able to re-trade the USDC for Bitcoin and
56+
pay off his loan with some USDC left over.
1457

15-
If, on the other hand, Bob is wrong -- the price of Bitcoin rises -- then Bob is in trouble. In this situation, the value of his borrowed Bitcoin may grow to exceed
16-
the `collateralFactor * valueOfSuppliedUSDC`. If Bob fails to pay off his loan before this happens, then Bob is subject to liquidation.
58+
If, on the other hand, Bob is wrong -- the price of Bitcoin rises -- then Bob is in
59+
trouble. In this situation, the value of his borrowed Bitcoin may grow to exceed
60+
the `collateralFactor * valueOfSuppliedUSDC`. If Bob fails to pay off his loan
61+
before this happens, then Bob is subject to liquidation.
1762

18-
For more introductory information, see [this paper](https://arxiv.org/pdf/1904.05234.pdf).
63+
For 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).
1964

2065
## Liquidation
2166

@@ -32,23 +77,36 @@ for (let cryptoToken of user.cryptoTokens) {
3277
const userIsLiquidatable = borrowValue > collatValue;
3378
```
3479

35-
The pseudocode above shows how Compound determines if a user is liquidatable or not. If they are liquidatable, the next question is "By how much?" The number that governs this is called the "close factor," and so far has been constant at 50%. This means that `liquidatableAmount <= borrowValue * 0.50`, but it's not the only constraint...
80+
The pseudocode above shows how Compound determines if a user is liquidatable or
81+
not. If they are liquidatable, the next question is "By how much?" The number
82+
that governs this is called the "close factor," and so far has been constant at
83+
50%. This means that `liquidatableAmount <= borrowValue * 0.50`, but it's not
84+
the only constraint...
3685

37-
If successful, liquidators receive a portion of the user's collateral: `revenue = liquidatableAmount * liquidationIncentive`, where the liquidation incentive is usually around 110%. In order for this to work, the user must actually have that much collateral available for the taking. This means that
38-
`liquidatableAmount <= collatValue * liquidationIncentive`.
86+
If successful, liquidators receive a portion of the user's collateral: `revenue = liquidatableAmount * liquidationIncentive`,
87+
where the liquidation incentive is usually around 110%. In order for this to work,
88+
the user must actually have that much collateral available for the taking. This
89+
means that `liquidatableAmount <= collatValue * liquidationIncentive`.
3990

40-
Both constraints must be satisfied for the liquidation to be successful. There are other things to consider as well, such as "Which loan should I pay off?" (if the
41-
user has borrowed multiple types of crypto tokens) and "Which collateral should I seize?" (if the user has supplied multiple types of crypto tokens). To complicate
42-
things further, DAI and USDT can be both repaid and seized in a single liquidation, but normally `repayTokenType != seizeTokenType`.
91+
Both constraints must be satisfied for the liquidation to be successful. There are
92+
other things to consider as well, such as "Which loan should I pay off?" (if the
93+
user has borrowed multiple types of crypto tokens) and "Which collateral should I
94+
seize?" (if the user has supplied multiple types of crypto tokens). To complicate
95+
things further, v2 cTokens can be both repaid and seized in a single liquidation,
96+
but normally `repayTokenType != seizeTokenType`.
4397

44-
You can find most of this liquidation logic [here](./src/database/tableusers.js) and [here](./src/candidate.js).
98+
You can find most of this liquidation logic [here](./src/database/tableusers.js) and [here](./src/messaging/candidate.js).
4599

46100
## Flash Loans
47101

48-
A flash loan is an atomic interaction (a single transaction on the blockchain) that (1) takes out a loan and (2) pays it off. Only certain Dapps allow this (e.g.
49-
AAVE, UniswapV2, and DyDx). What's great is that you can take out a loan of any size without first putting up collateral. If you fail to pay off your debt by the
50-
end of the transaction, the provider's software (AAVE, etc.) simply throws an error and the whole transaction is undone. The only penalty is the transaction fee
51-
(gas * gasPrice).
102+
> This section describes AAVE flash loans. Nantucket now uses Uniswap flash swaps, which work somewhat differently (and are more gas efficient in most cases)
103+
104+
A flash loan is an atomic interaction (a single transaction on the blockchain) that
105+
(1) takes out a loan and (2) pays it off. Only certain Dapps allow this (e.g. AAVE,
106+
UniswapV2, and DyDx). What's great is that you can take out a loan of any size
107+
without first putting up collateral. If you fail to pay off your debt by the
108+
end of the transaction, the provider's software (AAVE, etc.) simply throws an error
109+
and the whole transaction is undone. The only penalty is the transaction fee (gas * gasPrice).
52110

53111
Nantucket uses AAVE flash loans to liquidate users on Compound:
54112
1. Borrow X tokens of type A from AAVE
@@ -64,29 +122,51 @@ This logic can be found in the [contracts folder](./contracts). I've already dep
64122

65123
## Pipeline
66124

67-
Compound (the company) provides an HTTP endpoint that returns information about all users (address, supply amounts, and borrow amounts). They provide another HTTP
68-
endpoint that returns information about all tokens (address, real-world price, collateral factor). Nantucket periodically polls this information, does some
69-
computation, and stores it in a Postgres database. The "computation" is really just to answer the questions "Which type of token should I repay and seize if this
70-
user becomes liquidatable?" and "How profitable would this be?"
125+
> 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!!!**
71126
72-
A separate process periodically polls the database to get a subset of Compound users. This subset is configurable via the arguments passed to the `Main`
73-
constructor. Whenever a new block gets added to the Ethereum blockchain (~ every 15 seconds), Nantucket loops through the users to decide (1) if they are
74-
liquidatable according to the Compound Dapp and (2) if they are liquidatable according to token prices on Coinbase.
127+
Compound (the company) provides an HTTP endpoint that returns information about all
128+
users (address, supply amounts, and borrow amounts). They provide another HTTP
129+
endpoint that returns information about all tokens (address, real-world price,
130+
collateral factor). Nantucket periodically polls this information, does some
131+
computation, and stores it in a Postgres database. The "computation" is really just
132+
to answer the questions "Which type of token should I repay and seize if this
133+
user becomes liquidatable?" and "How profitable would this be?"
75134

76-
In case 1, the code immediately sends a transaction to liquidate them. The gas price of that transaction is
77-
`gasPriceRecommendedByGethBlockchainClient * someMultiplier` where `someMultiplier` is configurable in `Main`'s constructor. For example, if a `Main` instance is
78-
configured to look only at users where the potential profit is >$10000, the gas price multiplier may be set higher so that the transaction goes through faster --
135+
A separate process periodically polls the database to get a subset of Compound
136+
users. This subset is configurable via the arguments passed to the `Main`
137+
constructor. Whenever a new block gets added to the Ethereum blockchain (~ every 15
138+
seconds), Nantucket loops through the users to decide (1) if they are
139+
liquidatable according to the Compound Dapp and (2) if they are liquidatable
140+
according to token prices on Coinbase.
141+
142+
In case 1, the code immediately sends a transaction to liquidate them. The gas price
143+
of that transaction is
144+
`gasPriceRecommendedByGethBlockchainClient * someMultiplier` where `someMultiplier`
145+
is configurable in `Main`'s constructor. For example, if a `Main` instance is
146+
configured to look only at users where the potential profit is >$10000, the gas
147+
price multiplier may be set higher so that the transaction goes through faster --
79148
after all, there's lots of competition for these high value liquidations.
80149

81-
In case 2, the user is added to the "price wave" list. A transaction is sent with the intent that it will remain "pending" for a while. This is done by sending it
82-
with a relatively low gas price (high enough that miners keep it in the transaction pool, but low enough that it takes a while to be included in a block).
150+
In case 2, the user is added to the "price wave" list. A transaction is sent with
151+
the intent that it will remain "pending" for a while. This is done by sending it
152+
with a relatively low gas price (high enough that miners keep it in the transaction
153+
pool, but low enough that it takes a while to be included in a block).
83154

84-
Nantucket also watches for the signature of Compound's price update transactions. This is when Compound (the company) updates the Dapp's knowledge of token prices
85-
to match those of the real world (on Coinbase, for example). As soon as one of these price update transactions is pending, Nantucket raises the gas price of pending
86-
transactions to match the gas price of the price update transaction. In theory, this should make the transactions happen consecutively, guaranteeing that we win the
87-
first-come first-serve liquidation battle. **In practice, other liquidators manage to put themselves in that position more reliably, and Nantucket loses.**
155+
Nantucket also watches for the signature of Compound's price update transactions.
156+
This is when Compound (the company) updates the Dapp's knowledge of token prices
157+
to match those of the real world (on Coinbase, for example). As soon as one of these
158+
price update transactions is pending, Nantucket raises the gas price of pending
159+
transactions to match the gas price of the price update transaction. In theory, this
160+
should make the transactions happen consecutively, guaranteeing that we win the
161+
first-come first-serve liquidation battle. **In practice, other liquidators manage
162+
to put themselves in that position more reliably, and Nantucket loses.**
88163

89164
Key Files:
90165
- [Start](./src/start.js)
91-
- [Main](./src/main.js)
166+
- [Worker](./src/worker.js)
92167
- [TxManager](./src/network/webthree/txmanager.js)
168+
169+
## Usage and Disclaimer
170+
171+
Don't. You will almost certainly loose money. Feel free to admire the code or use it as
172+
a reference point, but please don't try to run it as-is.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"test": "tests"
88
},
99
"dependencies": {
10-
"@tensorflow/tfjs": "2.0.1",
1110
"big.js": "^5.2.2",
1211
"dotenv": "^8.2.0",
1312
"ethereumjs-tx": "^2.1.2",

0 commit comments

Comments
 (0)