-
Notifications
You must be signed in to change notification settings - Fork 3
/
oven-client.ts
304 lines (269 loc) · 10.8 KB
/
oven-client.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
import Address from './types/address'
import { TezosToolkit, TransactionWalletOperation } from '@taquito/taquito'
import { TempleWallet } from '@temple-wallet/dapp'
import { InMemorySigner } from '@taquito/signer'
import HarbingerClient from './harbinger-client'
import Mutez from './types/mutez'
import Shard from './types/shard'
import { TransactionOperation } from '@taquito/taquito/dist/types/operations/transaction-operation'
import StableCoinClient from './stable-coin-client'
import BigNumber from 'bignumber.js'
// Conversion constant to change mutez into shard precision.
// TODO(keefertaylor): Refactor into a utils class.
const MUTEZ_DIGITS = 6
const SHARD_DIGITS = 18
const MUTEZ_TO_SHARD = new BigNumber(Math.pow(10, SHARD_DIGITS - MUTEZ_DIGITS))
const SHARD_PRECISION = new BigNumber(Math.pow(10, SHARD_DIGITS))
/**
* Controls interaction with an Oven.
*/
export default class OvenClient {
/** A TezosToolkit */
private readonly tezos: TezosToolkit
/**
* Create a new OvenClient.
*
* @param nodeUrl The URL of the node to connect to.
* @param wallet The wallet which will interact with this Oven.
* @param ovenAddress The address of the oven.
* @param stableCoinClient The stable coin client
* @param harbingerClient The harbinger price oracle client
*/
public constructor(
nodeUrl: string,
wallet: InMemorySigner | TempleWallet,
public readonly ovenAddress: Address,
public readonly stableCoinClient: StableCoinClient,
public readonly harbingerClient: HarbingerClient,
) {
const tezos = new TezosToolkit(nodeUrl)
// TODO(keefertaylor): Refactor this to be less nonsense.
if (wallet instanceof InMemorySigner) {
// Wallet is InMemorySigner
tezos.setProvider({ signer: wallet })
} else {
// Wallet is Thanos Wallet
tezos.setWalletProvider(wallet)
}
this.tezos = tezos
}
/**
* Retrieve the utilization of collateral in the oven.
*
* This number represents how much of the user's maximum borrow limit is used. If the number is greater than 1, the
* user may be liquidated.
*
* Mathematically, this is defined as:
* collateral utilization = (amount borrowed) / (amount you can borrow)
* collateral utilization = = (borrowed kUSD) / (amount of XTZ in Oven * Price of XTZ / collateral requirement)
*
* @returns The collateral utilization ratio as an integer with 18 digits of precision (ex. 80% is represented as
* 800_000_000_000_000_000)
*/
public async getCollateralUtilization(): Promise<Shard> {
// Get XTZ price as a shard.
const { price } = await this.harbingerClient.getPriceData()
const priceShard = price.multipliedBy(MUTEZ_TO_SHARD) // 18 digits
// Get the current XTZ balanace of the oven.
const currentBalance = await this.getBalance() // 18 digits
// Get value of collateral as a shard.
const collateralValue = currentBalance
.multipliedBy(MUTEZ_TO_SHARD)
.multipliedBy(priceShard)
.dividedBy(SHARD_PRECISION) // 18 digits
// Get borrowed collateral as a shard.
const totalBorrowedTokens = await this.getTotalOutstandingTokens() // 18 digits
return new BigNumber(totalBorrowedTokens.times(Math.pow(10, SHARD_DIGITS)).dividedBy(collateralValue).toFixed(0))
}
/**
* Retrieve the collateralization ratio of the oven.
*
* @deprecated This method returns a number that isn't particularly useful and may be removed in a future version
* of this library. Please use `getCollateralUtilization` instead.
* TODO(keefertaylor): Remove this method.
*
* @returns The collateralization ratio as a shard.
*/
public async getCollateralizationRatio(): Promise<Shard> {
console.warn(
"This method is deprecated and probably isn't giving you the value you expect. Consider using `getCollateralUtilization` instead",
)
// Get XTZ price as a shard.
const { price } = await this.harbingerClient.getPriceData()
const priceShard = price.multipliedBy(MUTEZ_TO_SHARD)
// Get the current XTZ balanace of the oven.
const currentBalance = await this.getBalance()
// Get value of collateral as a shard.
const collateralValue = currentBalance.multipliedBy(MUTEZ_TO_SHARD).multipliedBy(priceShard)
const collateralValueInkUSD = collateralValue.multipliedBy(SHARD_PRECISION)
// Get borrowed collateral as a shard.
const totalBorrowedTokens = await this.getTotalOutstandingTokens()
// TODO(keefertaylor): Refactor this to utils for re-use.
return collateralValueInkUSD.dividedBy(totalBorrowedTokens).multipliedBy(new BigNumber(100))
}
/**
* Retrieve the baker for the oven.
*
* @returns The baker for the oven.
*/
public async getBaker(): Promise<Address | null> {
try {
return await this.tezos.rpc.getDelegate(this.ovenAddress)
} catch (e: any) {
// If 404 was received then the baker is actually just null.
// See:
// - https://github.com/ecadlabs/taquito/issues/556
// - https://gitlab.com/tezos/tezos/-/issues/490
if (e.status === 404) {
return null
}
// If another error occurred, rethrow.
throw e
}
}
/**
* Retrieve the owner of the oven.
*
* @returns The address which owns the oven.
*/
public async getOwner(ovenStorage: any | undefined = undefined): Promise<Address> {
const resolvedOvenStorage = ovenStorage ?? ((await (await this.tezos.wallet.at(this.ovenAddress)).storage()) as any)
return resolvedOvenStorage.owner
}
/**
* Retrieve the number of tokens borrowed against the oven.
*
* NOTE: This method does *NOT* include stability fees. Please see:
* `getStabilityFees` and `getTotalOutstandingTokens`.
*
* @returns The amount of tokens borrowed.
*/
public async getBorrowedTokens(ovenStorage: any | undefined = undefined): Promise<Shard> {
const resolvedOvenStorage = ovenStorage ?? ((await (await this.tezos.wallet.at(this.ovenAddress)).storage()) as any)
return resolvedOvenStorage.borrowedTokens
}
/**
* Retrieve the total number of tokens outstanding on the vault.
*
* This method includes the stability fees and borrowed tokens. For individual
* breakdowns, see `getStabilityFees` and `getBorrowedTokens`.
*
* @param time The time to calculate the values at. Defaults to the current time.
* @param ovenStorage The pre-fetched oven storage
* @returns The amount of tokens owed in stability fees.
*/
public async getTotalOutstandingTokens(
time: Date = new Date(),
ovenStorage: any | undefined = undefined,
): Promise<Shard> {
const stabilityFees = await this.getStabilityFees(time, ovenStorage)
const borrowedTokens = await this.getBorrowedTokens()
return stabilityFees.plus(borrowedTokens)
}
/**
* Retrieve the number of tokens owed in stability fees against the oven.
*
* @param time The time to calculate the values at. Defaults to the current time.
* @param ovenStorage The pre-fetched oven storage
* @returns Interest rate data for the system.
*/
public async getStabilityFees(time: Date = new Date(), ovenStorage: any | undefined = undefined): Promise<Shard> {
const resolvedOvenStorage = ovenStorage ?? ((await (await this.tezos.wallet.at(this.ovenAddress)).storage()) as any)
const stabilityFeeTokens: BigNumber = resolvedOvenStorage.stabilityFeeTokens
const interestData = await this.stableCoinClient.getInterestData(time)
const ovenInterestIndex: BigNumber = resolvedOvenStorage.interestIndex
const borrowedTokens = await this.getBorrowedTokens(ovenStorage)
const minterInterestIndex: BigNumber = interestData.globalInterestIndex
const ratio = minterInterestIndex.times(SHARD_PRECISION).div(ovenInterestIndex).integerValue()
const totalPrinciple = borrowedTokens.plus(stabilityFeeTokens)
const newTotalTokens = ratio.times(totalPrinciple).div(SHARD_PRECISION).integerValue()
return newTotalTokens.minus(borrowedTokens)
}
/**
* Query if the Oven is liquidated.
*
* @returns A boolean representing the liquidation state.
*/
public async isLiquidated(ovenStorage: any | undefined = undefined): Promise<boolean> {
const resolvedOvenStorage = ovenStorage ?? ((await (await this.tezos.wallet.at(this.ovenAddress)).storage()) as any)
return resolvedOvenStorage.isLiquidated
}
/**
* Get the balance of the oven.
*
* @returns The oven balance in mutez.
*/
public async getBalance(): Promise<Mutez> {
return await this.tezos.tz.getBalance(this.ovenAddress)
}
/**
* Set the baker of the oven.
*
* @param baker The baker for the oven.
* @returns The operation hash
*/
public async setBaker(baker: Address | null): Promise<TransactionOperation | TransactionWalletOperation> {
return this.invokeOvenMethod('setDelegate', baker)
}
/**
* Liquidate an Oven.
*
* @returns The operation hash.
*/
public async liquidate(): Promise<TransactionOperation | TransactionWalletOperation> {
return this.invokeOvenMethod('liquidate', [['unit']])
}
/**
* Borrow tokens against an Oven's collateral.
*
* @param tokens The number of tokens to borrow.
* @returns The operation hash.
*/
public async borrow(tokens: Shard): Promise<TransactionOperation | TransactionWalletOperation> {
return this.invokeOvenMethod('borrow', tokens)
}
/**
* Deposit XTZ into the Oven.
*
* @param mutez The amount of XTZ to deposit, specified in mutez.
* @returns The operation hash.
*/
public async deposit(mutez: Mutez): Promise<TransactionOperation | TransactionWalletOperation> {
return this.invokeOvenMethod('default', [['unit']], Number(mutez))
}
/**
* Withdraw XTZ from the Oven.
*
* @param mutez The amount of XTZ to withdraw, specified in mutez.
* @returns The operation hash.
*/
public async withdraw(mutez: Mutez): Promise<TransactionOperation | TransactionWalletOperation> {
return this.invokeOvenMethod('withdraw', mutez)
}
/**
* Repay borrowed tokens.
*
* @param tokensToRepay The number of tokens to repay.
* @returns The operation hash.
*/
public async repay(tokensToRepay: Shard): Promise<TransactionOperation | TransactionWalletOperation> {
return this.invokeOvenMethod('repay', tokensToRepay)
}
/**
* Invoke a method in the oven contract.
*
* @param entrypoint The entry point to invoke.
* @param args The arguments to send with the invocation.
* @param amount The amount of XTZ to send with the operation, specified in mutez.
* @returns The operation hash.
*/
private async invokeOvenMethod(
entrypoint: string,
args: any,
amount = 0,
): Promise<TransactionOperation | TransactionWalletOperation> {
const ovenContract = await this.tezos.wallet.at(this.ovenAddress)
const sendArgs = { amount: amount, mutez: true }
return await ovenContract.methods[entrypoint](args).send(sendArgs)
}
}