@strkfarm/sdk 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +34 -97
- package/dist/cli.mjs +34 -97
- package/dist/index.browser.global.js +4013 -4034
- package/dist/index.d.ts +31 -28
- package/dist/index.js +154 -174
- package/dist/index.mjs +150 -170
- package/package.json +2 -1
- package/src/global.ts +44 -2
- package/src/interfaces/common.ts +1 -1
- package/src/modules/pricer.ts +72 -19
- package/src/data/tokens.json +0 -79
package/dist/index.mjs
CHANGED
|
@@ -6,90 +6,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/modules/pricer.ts
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
// src/data/tokens.json
|
|
12
|
-
var tokens_default = [
|
|
13
|
-
{
|
|
14
|
-
name: "Ether",
|
|
15
|
-
symbol: "ETH",
|
|
16
|
-
address: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
|
|
17
|
-
decimals: 18,
|
|
18
|
-
pricerKey: "ETH-USDT"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
name: "USD Coin",
|
|
22
|
-
symbol: "USDC",
|
|
23
|
-
address: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
|
|
24
|
-
decimals: 6,
|
|
25
|
-
pricerKey: "USDC-USDT"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: "Wrapped BTC",
|
|
29
|
-
symbol: "WBTC",
|
|
30
|
-
address: "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
|
|
31
|
-
decimals: 8,
|
|
32
|
-
pricerKey: "WBTC-USDT"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: "Tether USD",
|
|
36
|
-
symbol: "USDT",
|
|
37
|
-
address: "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8",
|
|
38
|
-
decimals: 6,
|
|
39
|
-
pricerKey: "USDT-USDT"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
name: "Dai Stablecoin",
|
|
43
|
-
symbol: "DAIv0",
|
|
44
|
-
address: "",
|
|
45
|
-
decimals: 18,
|
|
46
|
-
pricerKey: "DAI-USDT"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: "Starknet Wrapped Staked Ether",
|
|
50
|
-
symbol: "wstETH",
|
|
51
|
-
address: "0x042b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2",
|
|
52
|
-
decimals: 18,
|
|
53
|
-
pricerKey: "wstETH-USDT"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: "Starknet Token",
|
|
57
|
-
symbol: "STRK",
|
|
58
|
-
address: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
|
|
59
|
-
decimals: 18,
|
|
60
|
-
pricerKey: "STRK-USDT"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: "zkLend Token",
|
|
64
|
-
symbol: "ZEND",
|
|
65
|
-
address: "",
|
|
66
|
-
decimals: 18,
|
|
67
|
-
pricerKey: "ZEND-USDT"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: "Dai Stablecoin",
|
|
71
|
-
symbol: "DAI",
|
|
72
|
-
address: "",
|
|
73
|
-
decimals: 18,
|
|
74
|
-
pricerKey: "DAI-USDT"
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: "Ekubo Protocol",
|
|
78
|
-
symbol: "EKUBO",
|
|
79
|
-
address: "",
|
|
80
|
-
decimals: 18,
|
|
81
|
-
pricerKey: "DAI-USDT"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: "kSTRK token",
|
|
85
|
-
symbol: "kSTRK",
|
|
86
|
-
address: "",
|
|
87
|
-
decimals: 18,
|
|
88
|
-
pricerKey: "DAI-USDT"
|
|
89
|
-
}
|
|
90
|
-
];
|
|
9
|
+
import axios2 from "axios";
|
|
91
10
|
|
|
92
11
|
// src/global.ts
|
|
12
|
+
import axios from "axios";
|
|
93
13
|
var logger = {
|
|
94
14
|
...console,
|
|
95
15
|
verbose(message) {
|
|
@@ -105,6 +25,7 @@ var FatalError = class extends Error {
|
|
|
105
25
|
this.name = "FatalError";
|
|
106
26
|
}
|
|
107
27
|
};
|
|
28
|
+
var tokens = [];
|
|
108
29
|
var Global = class {
|
|
109
30
|
static fatalError(message, err) {
|
|
110
31
|
logger.error(message);
|
|
@@ -118,7 +39,23 @@ var Global = class {
|
|
|
118
39
|
console.error(err);
|
|
119
40
|
}
|
|
120
41
|
static async getTokens() {
|
|
121
|
-
return
|
|
42
|
+
if (tokens.length) return tokens;
|
|
43
|
+
const data = await axios.get("https://starknet.api.avnu.fi/v1/starknet/tokens");
|
|
44
|
+
const tokensData = data.data.content;
|
|
45
|
+
tokensData.forEach((token) => {
|
|
46
|
+
if (!token.tags.includes("AVNU") || !token.tags.includes("Verified")) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
tokens.push({
|
|
50
|
+
name: token.name,
|
|
51
|
+
symbol: token.symbol,
|
|
52
|
+
address: token.address,
|
|
53
|
+
decimals: token.decimals,
|
|
54
|
+
coingeckId: token.extensions.coingeckoId
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
console.log(tokens);
|
|
58
|
+
return tokens;
|
|
122
59
|
}
|
|
123
60
|
static assert(condition, message) {
|
|
124
61
|
if (!condition) {
|
|
@@ -127,20 +64,89 @@ var Global = class {
|
|
|
127
64
|
}
|
|
128
65
|
};
|
|
129
66
|
|
|
67
|
+
// src/dataTypes/bignumber.ts
|
|
68
|
+
import BigNumber from "bignumber.js";
|
|
69
|
+
var Web3Number = class _Web3Number extends BigNumber {
|
|
70
|
+
constructor(value, decimals) {
|
|
71
|
+
super(value);
|
|
72
|
+
this.decimals = decimals;
|
|
73
|
+
}
|
|
74
|
+
static fromWei(weiNumber, decimals) {
|
|
75
|
+
const bn = new _Web3Number(weiNumber, decimals).dividedBy(10 ** decimals);
|
|
76
|
+
return new _Web3Number(bn.toString(), decimals);
|
|
77
|
+
}
|
|
78
|
+
toWei() {
|
|
79
|
+
return this.mul(10 ** this.decimals).toFixed(0);
|
|
80
|
+
}
|
|
81
|
+
multipliedBy(value) {
|
|
82
|
+
return new _Web3Number(this.mul(value).toString(), this.decimals);
|
|
83
|
+
}
|
|
84
|
+
dividedBy(value) {
|
|
85
|
+
return new _Web3Number(this.div(value).toString(), this.decimals);
|
|
86
|
+
}
|
|
87
|
+
plus(value) {
|
|
88
|
+
return new _Web3Number(this.add(value).toString(), this.decimals);
|
|
89
|
+
}
|
|
90
|
+
minus(n, base) {
|
|
91
|
+
return new _Web3Number(super.minus(n, base).toString(), this.decimals);
|
|
92
|
+
}
|
|
93
|
+
toString(base) {
|
|
94
|
+
return super.toString(base);
|
|
95
|
+
}
|
|
96
|
+
// [customInspectSymbol](depth: any, inspectOptions: any, inspect: any) {
|
|
97
|
+
// return this.toString();
|
|
98
|
+
// }
|
|
99
|
+
};
|
|
100
|
+
BigNumber.config({ DECIMAL_PLACES: 18 });
|
|
101
|
+
Web3Number.config({ DECIMAL_PLACES: 18 });
|
|
102
|
+
|
|
103
|
+
// src/dataTypes/address.ts
|
|
104
|
+
import { num } from "starknet";
|
|
105
|
+
var ContractAddr = class _ContractAddr {
|
|
106
|
+
constructor(address) {
|
|
107
|
+
this.address = _ContractAddr.standardise(address);
|
|
108
|
+
}
|
|
109
|
+
static from(address) {
|
|
110
|
+
return new _ContractAddr(address);
|
|
111
|
+
}
|
|
112
|
+
eq(other) {
|
|
113
|
+
return this.address === other.address;
|
|
114
|
+
}
|
|
115
|
+
eqString(other) {
|
|
116
|
+
return this.address === _ContractAddr.standardise(other);
|
|
117
|
+
}
|
|
118
|
+
static standardise(address) {
|
|
119
|
+
let _a = address;
|
|
120
|
+
if (!address) {
|
|
121
|
+
_a = "0";
|
|
122
|
+
}
|
|
123
|
+
const a = num.getHexString(num.getDecimalString(_a.toString()));
|
|
124
|
+
return a;
|
|
125
|
+
}
|
|
126
|
+
static eqString(a, b) {
|
|
127
|
+
return _ContractAddr.standardise(a) === _ContractAddr.standardise(b);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
130
131
|
// src/modules/pricer.ts
|
|
131
132
|
var CoinMarketCap = __require("coinmarketcap-api");
|
|
132
133
|
var Pricer = class {
|
|
133
|
-
constructor(config,
|
|
134
|
+
constructor(config, tokens2) {
|
|
134
135
|
this.tokens = [];
|
|
135
136
|
this.prices = {};
|
|
137
|
+
// code populates this map during runtime to determine which method to use for a given token
|
|
138
|
+
// The method set will be the first one to try after first attempt
|
|
139
|
+
this.methodToUse = {};
|
|
136
140
|
/**
|
|
137
141
|
* TOKENA and TOKENB are the two token names to get price of TokenA in terms of TokenB
|
|
138
142
|
*/
|
|
139
143
|
this.PRICE_API = `https://api.coinbase.com/v2/prices/{{PRICER_KEY}}/buy`;
|
|
140
|
-
|
|
144
|
+
this.EKUBO_API = "https://quoter-mainnet-api.ekubo.org/{{AMOUNT}}/{{TOKEN_ADDRESS}}/0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8";
|
|
145
|
+
// e.g. ETH/USDC
|
|
146
|
+
// backup oracle001
|
|
141
147
|
this.client = new CoinMarketCap(process.env.COINMARKETCAP_KEY);
|
|
142
148
|
this.config = config;
|
|
143
|
-
this.tokens =
|
|
149
|
+
this.tokens = tokens2;
|
|
144
150
|
}
|
|
145
151
|
isReady() {
|
|
146
152
|
const allPricesExist = Object.keys(this.prices).length === this.tokens.length;
|
|
@@ -224,34 +230,72 @@ var Pricer = class {
|
|
|
224
230
|
});
|
|
225
231
|
if (this.isReady() && this.config.heartbeatUrl) {
|
|
226
232
|
console.log(`sending beat`);
|
|
227
|
-
|
|
233
|
+
axios2.get(this.config.heartbeatUrl).catch((err) => {
|
|
228
234
|
console.error("Pricer: Heartbeat err", err);
|
|
229
235
|
});
|
|
230
236
|
}
|
|
231
237
|
}
|
|
232
|
-
async _getPrice(token) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
238
|
+
async _getPrice(token, defaultMethod = "all") {
|
|
239
|
+
const methodToUse = this.methodToUse[token.symbol] || defaultMethod;
|
|
240
|
+
logger.info(`Fetching price of ${token.symbol} using ${methodToUse}`);
|
|
241
|
+
switch (methodToUse) {
|
|
242
|
+
case "Coinbase":
|
|
243
|
+
try {
|
|
244
|
+
const result = await this._getPriceCoinbase(token);
|
|
245
|
+
this.methodToUse[token.symbol] = "Coinbase";
|
|
246
|
+
return result;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.warn(`Coinbase: price err: message [${token.symbol}]: `, error.message);
|
|
249
|
+
}
|
|
250
|
+
case "Coinmarketcap":
|
|
251
|
+
try {
|
|
252
|
+
const result = await this._getPriceCoinMarketCap(token);
|
|
253
|
+
this.methodToUse[token.symbol] = "Coinmarketcap";
|
|
254
|
+
return result;
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, Object.keys(error));
|
|
257
|
+
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, error.message);
|
|
258
|
+
}
|
|
259
|
+
case "Ekubo":
|
|
260
|
+
try {
|
|
261
|
+
const result = await this._getPriceEkubo(token);
|
|
262
|
+
this.methodToUse[token.symbol] = "Ekubo";
|
|
263
|
+
return result;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.warn(`Ekubo: price err [${token.symbol}]: `, error.message);
|
|
266
|
+
console.warn(`Ekubo: price err [${token.symbol}]: `, Object.keys(error));
|
|
267
|
+
}
|
|
236
268
|
}
|
|
237
|
-
|
|
238
|
-
return await this.
|
|
239
|
-
} catch (error) {
|
|
269
|
+
if (defaultMethod == "all") {
|
|
270
|
+
return await this._getPrice(token, "Coinbase");
|
|
240
271
|
}
|
|
241
|
-
throw new FatalError(`Price not found for ${token.
|
|
272
|
+
throw new FatalError(`Price not found for ${token.symbol}`);
|
|
242
273
|
}
|
|
243
274
|
async _getPriceCoinbase(token) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
const url = this.PRICE_API.replace("{{PRICER_KEY}}", token.pricerKey);
|
|
248
|
-
const result = await axios.get(url);
|
|
275
|
+
const url = this.PRICE_API.replace("{{PRICER_KEY}}", `${token.symbol}-USD`);
|
|
276
|
+
const result = await axios2.get(url);
|
|
249
277
|
const data = result.data;
|
|
250
278
|
return Number(data.data.amount);
|
|
251
279
|
}
|
|
252
280
|
async _getPriceCoinMarketCap(token) {
|
|
253
281
|
const result = await this.client.getQuotes({ symbol: token.symbol });
|
|
254
|
-
|
|
282
|
+
if (result.data)
|
|
283
|
+
return result.data[token.symbol].quote.USD.price;
|
|
284
|
+
throw new Error(result);
|
|
285
|
+
}
|
|
286
|
+
async _getPriceEkubo(token, amountIn = new Web3Number(1, token.decimals), retry = 0) {
|
|
287
|
+
const url = this.EKUBO_API.replace("{{TOKEN_ADDRESS}}", token.address).replace("{{AMOUNT}}", amountIn.toWei());
|
|
288
|
+
const result = await axios2.get(url);
|
|
289
|
+
const data = result.data;
|
|
290
|
+
const outputUSDC = Number(Web3Number.fromWei(data.total_calculated, 6).toFixed(6));
|
|
291
|
+
logger.verbose(`Ekubo: ${token.symbol} -> USDC: ${outputUSDC}, retry: ${retry}`);
|
|
292
|
+
if (outputUSDC === 0 && retry < 3) {
|
|
293
|
+
const amountIn2 = new Web3Number(100, token.decimals);
|
|
294
|
+
return await this._getPriceEkubo(token, amountIn2, retry + 1);
|
|
295
|
+
}
|
|
296
|
+
const usdcPrice = (await this.getPrice("USDC")).price;
|
|
297
|
+
logger.verbose(`USDC Price: ${usdcPrice}`);
|
|
298
|
+
return outputUSDC * usdcPrice;
|
|
255
299
|
}
|
|
256
300
|
};
|
|
257
301
|
|
|
@@ -374,43 +418,7 @@ var Pragma = class {
|
|
|
374
418
|
};
|
|
375
419
|
|
|
376
420
|
// src/modules/zkLend.ts
|
|
377
|
-
import
|
|
378
|
-
|
|
379
|
-
// src/dataTypes/bignumber.ts
|
|
380
|
-
import BigNumber from "bignumber.js";
|
|
381
|
-
var Web3Number = class _Web3Number extends BigNumber {
|
|
382
|
-
constructor(value, decimals) {
|
|
383
|
-
super(value);
|
|
384
|
-
this.decimals = decimals;
|
|
385
|
-
}
|
|
386
|
-
static fromWei(weiNumber, decimals) {
|
|
387
|
-
const bn = new _Web3Number(weiNumber, decimals).dividedBy(10 ** decimals);
|
|
388
|
-
return new _Web3Number(bn.toString(), decimals);
|
|
389
|
-
}
|
|
390
|
-
toWei() {
|
|
391
|
-
return this.mul(10 ** this.decimals).toFixed(0);
|
|
392
|
-
}
|
|
393
|
-
multipliedBy(value) {
|
|
394
|
-
return new _Web3Number(this.mul(value).toString(), this.decimals);
|
|
395
|
-
}
|
|
396
|
-
dividedBy(value) {
|
|
397
|
-
return new _Web3Number(this.div(value).toString(), this.decimals);
|
|
398
|
-
}
|
|
399
|
-
plus(value) {
|
|
400
|
-
return new _Web3Number(this.add(value).toString(), this.decimals);
|
|
401
|
-
}
|
|
402
|
-
minus(n, base) {
|
|
403
|
-
return new _Web3Number(super.minus(n, base).toString(), this.decimals);
|
|
404
|
-
}
|
|
405
|
-
toString(base) {
|
|
406
|
-
return super.toString(base);
|
|
407
|
-
}
|
|
408
|
-
// [customInspectSymbol](depth: any, inspectOptions: any, inspect: any) {
|
|
409
|
-
// return this.toString();
|
|
410
|
-
// }
|
|
411
|
-
};
|
|
412
|
-
BigNumber.config({ DECIMAL_PLACES: 18 });
|
|
413
|
-
Web3Number.config({ DECIMAL_PLACES: 18 });
|
|
421
|
+
import axios3 from "axios";
|
|
414
422
|
|
|
415
423
|
// src/interfaces/lending.ts
|
|
416
424
|
var MarginType = /* @__PURE__ */ ((MarginType2) => {
|
|
@@ -454,7 +462,7 @@ var _ZkLend = class _ZkLend extends ILending {
|
|
|
454
462
|
async init() {
|
|
455
463
|
try {
|
|
456
464
|
logger.verbose(`Initialising ${this.metadata.name}`);
|
|
457
|
-
const result = await
|
|
465
|
+
const result = await axios3.get(_ZkLend.POOLS_URL);
|
|
458
466
|
const data = result.data;
|
|
459
467
|
const savedTokens = await Global.getTokens();
|
|
460
468
|
data.forEach((pool) => {
|
|
@@ -544,7 +552,7 @@ var _ZkLend = class _ZkLend extends ILending {
|
|
|
544
552
|
*/
|
|
545
553
|
async getPositions(user) {
|
|
546
554
|
const url = this.POSITION_URL.replace("{{USER_ADDR}}", user.address);
|
|
547
|
-
const result = await
|
|
555
|
+
const result = await axios3.get(url);
|
|
548
556
|
const data = result.data;
|
|
549
557
|
const lendingPosition = [];
|
|
550
558
|
logger.verbose(`${this.metadata.name}:: Positions: ${JSON.stringify(data)}`);
|
|
@@ -613,34 +621,6 @@ var Initializable = class {
|
|
|
613
621
|
}
|
|
614
622
|
};
|
|
615
623
|
|
|
616
|
-
// src/dataTypes/address.ts
|
|
617
|
-
import { num } from "starknet";
|
|
618
|
-
var ContractAddr = class _ContractAddr {
|
|
619
|
-
constructor(address) {
|
|
620
|
-
this.address = _ContractAddr.standardise(address);
|
|
621
|
-
}
|
|
622
|
-
static from(address) {
|
|
623
|
-
return new _ContractAddr(address);
|
|
624
|
-
}
|
|
625
|
-
eq(other) {
|
|
626
|
-
return this.address === other.address;
|
|
627
|
-
}
|
|
628
|
-
eqString(other) {
|
|
629
|
-
return this.address === _ContractAddr.standardise(other);
|
|
630
|
-
}
|
|
631
|
-
static standardise(address) {
|
|
632
|
-
let _a = address;
|
|
633
|
-
if (!address) {
|
|
634
|
-
_a = "0";
|
|
635
|
-
}
|
|
636
|
-
const a = num.getHexString(num.getDecimalString(_a.toString()));
|
|
637
|
-
return a;
|
|
638
|
-
}
|
|
639
|
-
static eqString(a, b) {
|
|
640
|
-
return _ContractAddr.standardise(a) === _ContractAddr.standardise(b);
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
|
|
644
624
|
// src/strategies/autoCompounderStrk.ts
|
|
645
625
|
import { Contract as Contract2, uint256 } from "starknet";
|
|
646
626
|
var AutoCompounderSTRK = class {
|
|
@@ -908,8 +888,8 @@ var Store = class _Store {
|
|
|
908
888
|
// src/node/pricer-redis.ts
|
|
909
889
|
import { createClient } from "redis";
|
|
910
890
|
var PricerRedis = class extends Pricer {
|
|
911
|
-
constructor(config,
|
|
912
|
-
super(config,
|
|
891
|
+
constructor(config, tokens2) {
|
|
892
|
+
super(config, tokens2);
|
|
913
893
|
this.redisClient = null;
|
|
914
894
|
}
|
|
915
895
|
/** Reads prices from Pricer._loadPrices and uses a callback to set prices in redis */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strkfarm/sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "STRKFarm TS SDK (Meant for our internal use, but feel free to use it)",
|
|
5
5
|
"typings": "dist/index.d.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"inquirer": "^10.1.2",
|
|
54
54
|
"node-telegram-bot-api": "^0.66.0",
|
|
55
55
|
"redis": "^4.7.0",
|
|
56
|
+
"stacktrace-js": "^2.0.2",
|
|
56
57
|
"starknet": "^6.11.0",
|
|
57
58
|
"winston": "^3.13.0"
|
|
58
59
|
}
|
package/src/global.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
1
2
|
import { TokenInfo } from './interfaces';
|
|
2
|
-
import TOKENS from '@/data/tokens.json';
|
|
3
3
|
|
|
4
4
|
const colors = {
|
|
5
5
|
error: 'red',
|
|
@@ -44,6 +44,9 @@ export class FatalError extends Error {
|
|
|
44
44
|
this.name = "FatalError";
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
const tokens: TokenInfo[] = [];
|
|
49
|
+
|
|
47
50
|
/** Contains globally useful functions.
|
|
48
51
|
* - fatalError: Things to do when a fatal error occurs
|
|
49
52
|
*/
|
|
@@ -62,7 +65,46 @@ export class Global {
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
static async getTokens(): Promise<TokenInfo[]> {
|
|
65
|
-
return
|
|
68
|
+
if (tokens.length) return tokens;
|
|
69
|
+
|
|
70
|
+
// fetch from avnu API
|
|
71
|
+
const data = await axios.get('https://starknet.api.avnu.fi/v1/starknet/tokens');
|
|
72
|
+
const tokensData = data.data.content;
|
|
73
|
+
|
|
74
|
+
// Array of the following is returned
|
|
75
|
+
// {
|
|
76
|
+
// "name": "USD Coin",
|
|
77
|
+
// "address": "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
|
|
78
|
+
// "symbol": "USDC",
|
|
79
|
+
// "decimals": 6,
|
|
80
|
+
// "logoUri": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
|
|
81
|
+
// "lastDailyVolumeUsd": 2964287916.82621,
|
|
82
|
+
// "extensions": {
|
|
83
|
+
// "coingeckoId": "usd-coin"
|
|
84
|
+
// },
|
|
85
|
+
// "tags": [
|
|
86
|
+
// "AVNU",
|
|
87
|
+
// "Verified"
|
|
88
|
+
// ]
|
|
89
|
+
// }
|
|
90
|
+
|
|
91
|
+
tokensData.forEach((token: any) => {
|
|
92
|
+
// if tags do not contain Avnu and verified, ignore
|
|
93
|
+
// This would exclude meme coins for now
|
|
94
|
+
if (!token.tags.includes('AVNU') || !token.tags.includes('Verified')) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
tokens.push({
|
|
99
|
+
name: token.name,
|
|
100
|
+
symbol: token.symbol,
|
|
101
|
+
address: token.address,
|
|
102
|
+
decimals: token.decimals,
|
|
103
|
+
coingeckId: token.extensions.coingeckoId,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
console.log(tokens);
|
|
107
|
+
return tokens;
|
|
66
108
|
}
|
|
67
109
|
|
|
68
110
|
static assert(condition: any, message: string) {
|
package/src/interfaces/common.ts
CHANGED
package/src/modules/pricer.ts
CHANGED
|
@@ -2,6 +2,7 @@ import axios from "axios";
|
|
|
2
2
|
import { FatalError, Global, logger } from "@/global";
|
|
3
3
|
import { TokenInfo } from "@/interfaces/common";
|
|
4
4
|
import { IConfig } from "@/interfaces/common";
|
|
5
|
+
import { Web3Number } from "@/dataTypes";
|
|
5
6
|
const CoinMarketCap = require('coinmarketcap-api')
|
|
6
7
|
|
|
7
8
|
export interface PriceInfo {
|
|
@@ -15,14 +16,19 @@ export class Pricer {
|
|
|
15
16
|
[key: string]: PriceInfo
|
|
16
17
|
} = {}
|
|
17
18
|
|
|
19
|
+
// code populates this map during runtime to determine which method to use for a given token
|
|
20
|
+
// The method set will be the first one to try after first attempt
|
|
21
|
+
private methodToUse: {[tokenSymbol: string]: 'Ekubo' | 'Coinbase' | 'Coinmarketcap'} = {};
|
|
22
|
+
|
|
18
23
|
/**
|
|
19
24
|
* TOKENA and TOKENB are the two token names to get price of TokenA in terms of TokenB
|
|
20
25
|
*/
|
|
21
26
|
protected PRICE_API = `https://api.coinbase.com/v2/prices/{{PRICER_KEY}}/buy`;
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
protected EKUBO_API = 'https://quoter-mainnet-api.ekubo.org/{{AMOUNT}}/{{TOKEN_ADDRESS}}/0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8'; // e.g. ETH/USDC
|
|
28
|
+
|
|
29
|
+
// backup oracle001
|
|
24
30
|
protected client = new CoinMarketCap(process.env.COINMARKETCAP_KEY!);
|
|
25
|
-
|
|
31
|
+
|
|
26
32
|
constructor(config: IConfig, tokens: TokenInfo[]) {
|
|
27
33
|
this.config = config;
|
|
28
34
|
this.tokens = tokens;
|
|
@@ -123,34 +129,81 @@ export class Pricer {
|
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
async _getPrice(token: TokenInfo) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
async _getPrice(token: TokenInfo, defaultMethod = 'all'): Promise<number> {
|
|
133
|
+
const methodToUse: string = this.methodToUse[token.symbol] || defaultMethod; // default start with coinbase
|
|
134
|
+
logger.info(`Fetching price of ${token.symbol} using ${methodToUse}`);
|
|
135
|
+
switch (methodToUse) {
|
|
136
|
+
case 'Coinbase':
|
|
137
|
+
try {
|
|
138
|
+
const result = await this._getPriceCoinbase(token);
|
|
139
|
+
this.methodToUse[token.symbol] = 'Coinbase';
|
|
140
|
+
return result;
|
|
141
|
+
} catch (error: any) {
|
|
142
|
+
console.warn(`Coinbase: price err: message [${token.symbol}]: `, error.message);
|
|
143
|
+
// do nothing, try next
|
|
144
|
+
}
|
|
145
|
+
case 'Coinmarketcap':
|
|
146
|
+
try {
|
|
147
|
+
const result = await this._getPriceCoinMarketCap(token);
|
|
148
|
+
this.methodToUse[token.symbol] = 'Coinmarketcap';
|
|
149
|
+
return result;
|
|
150
|
+
} catch (error: any) {
|
|
151
|
+
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, Object.keys(error));
|
|
152
|
+
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, error.message);
|
|
153
|
+
}
|
|
154
|
+
case 'Ekubo':
|
|
155
|
+
try {
|
|
156
|
+
const result = await this._getPriceEkubo(token);
|
|
157
|
+
this.methodToUse[token.symbol] = 'Ekubo';
|
|
158
|
+
return result;
|
|
159
|
+
} catch (error: any) {
|
|
160
|
+
console.warn(`Ekubo: price err [${token.symbol}]: `, error.message);
|
|
161
|
+
console.warn(`Ekubo: price err [${token.symbol}]: `, Object.keys(error));
|
|
162
|
+
// do nothing, try next
|
|
163
|
+
}
|
|
131
164
|
}
|
|
132
165
|
|
|
133
|
-
try
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
166
|
+
// if methodToUse is the default one, pass Coinbase to try all from start
|
|
167
|
+
if (defaultMethod == 'all') {
|
|
168
|
+
// try again with coinbase
|
|
169
|
+
return await this._getPrice(token, 'Coinbase');
|
|
137
170
|
}
|
|
138
171
|
|
|
139
|
-
throw new FatalError(`Price not found for ${token.
|
|
172
|
+
throw new FatalError(`Price not found for ${token.symbol}`);
|
|
140
173
|
}
|
|
141
174
|
|
|
142
175
|
async _getPriceCoinbase(token: TokenInfo) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
const url = this.PRICE_API.replace("{{PRICER_KEY}}", token.pricerKey);
|
|
147
|
-
const result = await axios.get(url);
|
|
176
|
+
const url = this.PRICE_API.replace("{{PRICER_KEY}}", `${token.symbol}-USD`);
|
|
177
|
+
const result = await axios.get(url)
|
|
148
178
|
const data: any = result.data;
|
|
149
179
|
return Number(data.data.amount);
|
|
150
180
|
}
|
|
151
181
|
|
|
152
182
|
async _getPriceCoinMarketCap(token: TokenInfo): Promise<number> {
|
|
153
183
|
const result = await this.client.getQuotes({symbol: token.symbol});
|
|
154
|
-
|
|
184
|
+
if (result.data)
|
|
185
|
+
return result.data[token.symbol].quote.USD.price as number
|
|
186
|
+
|
|
187
|
+
throw new Error(result);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async _getPriceEkubo(token: TokenInfo, amountIn = new Web3Number(1, token.decimals), retry = 0): Promise<number> {
|
|
191
|
+
const url = this.EKUBO_API.replace("{{TOKEN_ADDRESS}}", token.address).replace("{{AMOUNT}}", amountIn.toWei());
|
|
192
|
+
const result = await axios.get(url);
|
|
193
|
+
const data: any = result.data;
|
|
194
|
+
const outputUSDC = Number(Web3Number.fromWei(data.total_calculated, 6).toFixed(6));
|
|
195
|
+
logger.verbose(`Ekubo: ${token.symbol} -> USDC: ${outputUSDC}, retry: ${retry}`);
|
|
196
|
+
if (outputUSDC === 0 && retry < 3) {
|
|
197
|
+
// try again with a higher amount
|
|
198
|
+
const amountIn = new Web3Number(100, token.decimals); // 100 unit of token
|
|
199
|
+
return await this._getPriceEkubo(token, amountIn, retry + 1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// if usdc depegs, it will not longer be 1 USD
|
|
203
|
+
// so we need to get the price of USDC in USD
|
|
204
|
+
// and then convert the outputUSDC to USD
|
|
205
|
+
const usdcPrice = (await this.getPrice('USDC')).price;
|
|
206
|
+
logger.verbose(`USDC Price: ${usdcPrice}`);
|
|
207
|
+
return outputUSDC * usdcPrice;
|
|
155
208
|
}
|
|
156
209
|
}
|