You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- 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
+
5
39
## Introduction
6
40
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
+
7
44
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
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.
14
57
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.
17
62
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).
19
64
20
65
## Liquidation
21
66
@@ -32,23 +77,36 @@ for (let cryptoToken of user.cryptoTokens) {
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...
36
85
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
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`.
39
90
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`.
43
97
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).
45
99
46
100
## Flash Loans
47
101
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).
52
110
53
111
Nantucket uses AAVE flash loans to liquidate users on Compound:
54
112
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
64
122
65
123
## Pipeline
66
124
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!!!**
71
126
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?"
75
134
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 --
79
148
after all, there's lots of competition for these high value liquidations.
80
149
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).
83
154
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.**
88
163
89
164
Key Files:
90
165
-[Start](./src/start.js)
91
-
-[Main](./src/main.js)
166
+
-[Worker](./src/worker.js)
92
167
-[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.
0 commit comments