@merkl/api 0.18.4 → 0.18.6
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/src/eden/index.d.ts +39 -19
- package/dist/src/index.d.ts +7 -3
- package/dist/src/modules/v4/price/price.controller.js +1 -1
- package/dist/src/modules/v4/price/price.repository.d.ts +4 -1
- package/dist/src/modules/v4/price/price.repository.js +6 -2
- package/dist/src/modules/v4/price/price.service.d.ts +4 -1
- package/dist/src/modules/v4/price/price.service.js +2 -2
- package/dist/src/modules/v4/router.d.ts +7 -3
- package/dist/src/modules/v4/token/token.controller.d.ts +7 -3
- package/dist/src/modules/v4/token/token.controller.js +5 -1
- package/dist/src/modules/v4/token/token.model.d.ts +1 -0
- package/dist/src/modules/v4/token/token.model.js +1 -0
- package/dist/src/modules/v4/token/token.repository.js +29 -28
- package/dist/src/modules/v4/token/token.service.d.ts +7 -5
- package/dist/src/modules/v4/token/token.service.js +96 -84
- package/dist/src/routes/v3/app.js +1 -1
- package/dist/src/utils/prices/priceService.js +1 -1
- package/dist/tsconfig.package.tsbuildinfo +1 -1
- package/package.json +1 -1
@@ -3,6 +3,7 @@ import { getTokensListWithCache } from "@/libs/getTokensList";
|
|
3
3
|
import { getOnlyUserBalance } from "@/libs/tokens/balances";
|
4
4
|
import { log } from "@/utils/logger";
|
5
5
|
import { apiDbClient } from "@/utils/prisma";
|
6
|
+
import { throwOnInvalidRequiredAddress, throwOnUnsupportedChainId } from "@/utils/throw";
|
6
7
|
import { Prisma } from "@db/api";
|
7
8
|
import { ChainInteractionService, DistributionCreatorService, NETWORK_LABELS, bigIntToNumber, } from "@sdk";
|
8
9
|
import { getAddress, parseUnits } from "viem";
|
@@ -71,7 +72,6 @@ export class TokenService {
|
|
71
72
|
}
|
72
73
|
static async fetchOnChain(token) {
|
73
74
|
const onchainData = await TokenRepository.getTokenInfo(token);
|
74
|
-
//TODO: find a way to get the icon
|
75
75
|
return {
|
76
76
|
chainId: token.chainId,
|
77
77
|
address: token.address,
|
@@ -79,9 +79,19 @@ export class TokenService {
|
|
79
79
|
...Object.assign({ name: "unknown", decimals: 18, symbol: "UNKNOWN", verified: false, isTest: false, isNative: false }, onchainData),
|
80
80
|
};
|
81
81
|
}
|
82
|
+
static async tryToFillWithCoingeckoIcons() {
|
83
|
+
// 1 - Find coingecko price sources
|
84
|
+
let priceSources = await PriceService.findManyPriceSources({ method: "COINGECKO" });
|
85
|
+
// 2 - Find tokens with missing logos
|
86
|
+
const missingIcons = await TokenService.findMany({ missingIcons: true });
|
87
|
+
// 3 - Do the intersection of both
|
88
|
+
priceSources = priceSources.filter(p => missingIcons.find(t => t.symbol === p.symbol || t.address === p.symbol));
|
89
|
+
log.info(`found ${priceSources.length} tokens with missing icons on coingecko.`);
|
90
|
+
// 4 - Loop through each and try to get the icon
|
91
|
+
}
|
82
92
|
static async fetchManyOnChain(chainId, addresses) {
|
83
93
|
const tokens = {};
|
84
|
-
//Batch onchain calls together when multiples
|
94
|
+
// Batch onchain calls together when multiples
|
85
95
|
for (const address of addresses) {
|
86
96
|
tokens[address] = await TokenService.fetchOnChain({ chainId, address });
|
87
97
|
}
|
@@ -127,77 +137,6 @@ export class TokenService {
|
|
127
137
|
await TokenService.updateAddressPrices(address, price);
|
128
138
|
}
|
129
139
|
}
|
130
|
-
/**
|
131
|
-
* Get all tokens from https://github.com/AngleProtocol/angle-token-list and override icons from it
|
132
|
-
* TODO: use the bucket
|
133
|
-
*/
|
134
|
-
static async fillTokenAndIconsFromTokenList() {
|
135
|
-
const tokenList = await getTokensListWithCache();
|
136
|
-
for (const chain of Object.keys(tokenList)) {
|
137
|
-
for (const [symbol, token] of Object.entries(tokenList[chain])) {
|
138
|
-
if (!(await TokenRepository.findUnique(TokenService.hashId({ chainId: Number.parseInt(chain), address: token.address })))) {
|
139
|
-
try {
|
140
|
-
const res = await TokenRepository.create({
|
141
|
-
id: TokenService.hashId({ chainId: Number.parseInt(chain), address: token.address }),
|
142
|
-
chainId: Number.parseInt(chain),
|
143
|
-
address: token.address,
|
144
|
-
name: token.name,
|
145
|
-
symbol: token.symbol,
|
146
|
-
verified: true,
|
147
|
-
decimals: token.decimals,
|
148
|
-
icon: token.logoURI,
|
149
|
-
isTest: false,
|
150
|
-
isNative: false,
|
151
|
-
});
|
152
|
-
log.local(`Token created: ${res?.symbol} on ${NETWORK_LABELS[Number.parseInt(chain)]}`);
|
153
|
-
}
|
154
|
-
catch (e) {
|
155
|
-
console.error(e);
|
156
|
-
}
|
157
|
-
}
|
158
|
-
try {
|
159
|
-
await apiDbClient.token.update({
|
160
|
-
data: {
|
161
|
-
chainId: Number.parseInt(chain),
|
162
|
-
address: token.address,
|
163
|
-
name: token.name,
|
164
|
-
symbol: token.symbol,
|
165
|
-
verified: true,
|
166
|
-
decimals: token.decimals,
|
167
|
-
icon: token.logoURI,
|
168
|
-
},
|
169
|
-
where: {
|
170
|
-
chainId_address: {
|
171
|
-
chainId: Number.parseInt(chain),
|
172
|
-
address: token.address,
|
173
|
-
},
|
174
|
-
},
|
175
|
-
});
|
176
|
-
}
|
177
|
-
catch (e) {
|
178
|
-
console.error(e);
|
179
|
-
}
|
180
|
-
try {
|
181
|
-
const tokensWithSameSymbol = await apiDbClient.token.findMany({
|
182
|
-
select: { chainId: true, address: true },
|
183
|
-
where: { symbol: { equals: symbol, mode: "insensitive" } },
|
184
|
-
});
|
185
|
-
for (const dbToken of tokensWithSameSymbol) {
|
186
|
-
await apiDbClient.token.update({
|
187
|
-
data: { icon: token.logoURI },
|
188
|
-
where: {
|
189
|
-
chainId_address: {
|
190
|
-
chainId: dbToken.chainId,
|
191
|
-
address: dbToken.address,
|
192
|
-
},
|
193
|
-
},
|
194
|
-
});
|
195
|
-
}
|
196
|
-
}
|
197
|
-
catch (_err) { }
|
198
|
-
}
|
199
|
-
}
|
200
|
-
}
|
201
140
|
/**
|
202
141
|
* Create token on database
|
203
142
|
* @param chainId
|
@@ -320,24 +259,21 @@ export class TokenService {
|
|
320
259
|
}, chainId);
|
321
260
|
}
|
322
261
|
static async update(id, data) {
|
323
|
-
// let iconUri = data.icon;
|
324
|
-
// if (data.iconFile) {
|
325
|
-
// iconUri = await BucketService.upload("merkl-assets", `/tokens/${id}`, data.iconFile.stream(), true);
|
326
|
-
// }
|
327
262
|
return await TokenRepository.update(id, data);
|
328
263
|
}
|
329
264
|
static async notionWebhook(body) {
|
330
265
|
const env = process.env.ENV === "prod" ? "production" : process.env.ENV;
|
331
266
|
const bucket = new BucketService(`merkl-${env}-tokens`, `angle-${env}-1`);
|
332
267
|
const properties = body.data.properties;
|
333
|
-
const icon = properties
|
268
|
+
const icon = properties.Icon.files?.[0]?.file.url;
|
334
269
|
const iconFile = await fetch(icon);
|
335
270
|
const mimeType = iconFile.headers.get("content-type");
|
336
271
|
const extension = mimeType.split("/")[1];
|
337
|
-
const address = properties
|
272
|
+
const address = throwOnInvalidRequiredAddress(properties.Address.rich_text[0].plain_text);
|
338
273
|
const chainId = properties["Chain ID"].number;
|
339
|
-
|
340
|
-
const
|
274
|
+
throwOnUnsupportedChainId(chainId);
|
275
|
+
const displaySymbol = properties.Symbol.rich_text[0]?.plain_text;
|
276
|
+
const isVerified = properties.Verified.checkbox;
|
341
277
|
const coingeckoApiId = properties["CoinGecko API ID"].rich_text[0].plain_text;
|
342
278
|
const byteArray = await iconFile.bytes();
|
343
279
|
const [token] = await TokenService.getManyOrCreate([
|
@@ -347,7 +283,7 @@ export class TokenService {
|
|
347
283
|
},
|
348
284
|
]);
|
349
285
|
if (!token || !token.name)
|
350
|
-
throw new HttpError(`Failed to fetch on-chain data for token ${symbol} (${address} on chainId ${chainId}).`);
|
286
|
+
throw new HttpError(`Failed to fetch on-chain data for token ${token?.symbol} (${address} on chainId ${chainId}).`);
|
351
287
|
try {
|
352
288
|
await bucket.pushRaw(`${chainId}/${address}.${extension}`, byteArray, {
|
353
289
|
type: mimeType,
|
@@ -361,7 +297,11 @@ export class TokenService {
|
|
361
297
|
}
|
362
298
|
if (coingeckoApiId) {
|
363
299
|
try {
|
364
|
-
await PriceService.createPriceSource({
|
300
|
+
await PriceService.createPriceSource({
|
301
|
+
method: "COINGECKO",
|
302
|
+
symbol: token.symbol,
|
303
|
+
args: { ticker: coingeckoApiId },
|
304
|
+
});
|
365
305
|
}
|
366
306
|
catch (err) {
|
367
307
|
console.error("Failed to create price source.");
|
@@ -370,9 +310,81 @@ export class TokenService {
|
|
370
310
|
}
|
371
311
|
return await TokenService.update(token.id, {
|
372
312
|
icon: token.icon,
|
373
|
-
displaySymbol:
|
313
|
+
displaySymbol: displaySymbol && displaySymbol !== "" ? displaySymbol : token.symbol,
|
374
314
|
name: token.name,
|
375
315
|
verified: isVerified,
|
376
316
|
});
|
377
317
|
}
|
318
|
+
/**
|
319
|
+
* Get all tokens from https://github.com/AngleProtocol/angle-token-list and override icons from it
|
320
|
+
* TODO: use the bucket
|
321
|
+
* @deprecated Should be useless now that the token list is not used anymore
|
322
|
+
*/
|
323
|
+
static async fillTokenAndIconsFromTokenList() {
|
324
|
+
const tokenList = await getTokensListWithCache();
|
325
|
+
for (const chain of Object.keys(tokenList)) {
|
326
|
+
for (const [symbol, token] of Object.entries(tokenList[chain])) {
|
327
|
+
if (!(await TokenRepository.findUnique(TokenService.hashId({ chainId: Number.parseInt(chain), address: token.address })))) {
|
328
|
+
try {
|
329
|
+
const res = await TokenRepository.create({
|
330
|
+
id: TokenService.hashId({ chainId: Number.parseInt(chain), address: token.address }),
|
331
|
+
chainId: Number.parseInt(chain),
|
332
|
+
address: token.address,
|
333
|
+
name: token.name,
|
334
|
+
symbol: token.symbol,
|
335
|
+
verified: true,
|
336
|
+
decimals: token.decimals,
|
337
|
+
icon: token.logoURI,
|
338
|
+
isTest: false,
|
339
|
+
isNative: false,
|
340
|
+
});
|
341
|
+
log.local(`Token created: ${res?.symbol} on ${NETWORK_LABELS[Number.parseInt(chain)]}`);
|
342
|
+
}
|
343
|
+
catch (e) {
|
344
|
+
console.error(e);
|
345
|
+
}
|
346
|
+
}
|
347
|
+
try {
|
348
|
+
await apiDbClient.token.update({
|
349
|
+
data: {
|
350
|
+
chainId: Number.parseInt(chain),
|
351
|
+
address: token.address,
|
352
|
+
name: token.name,
|
353
|
+
symbol: token.symbol,
|
354
|
+
verified: true,
|
355
|
+
decimals: token.decimals,
|
356
|
+
icon: token.logoURI,
|
357
|
+
},
|
358
|
+
where: {
|
359
|
+
chainId_address: {
|
360
|
+
chainId: Number.parseInt(chain),
|
361
|
+
address: token.address,
|
362
|
+
},
|
363
|
+
},
|
364
|
+
});
|
365
|
+
}
|
366
|
+
catch (e) {
|
367
|
+
console.error(e);
|
368
|
+
}
|
369
|
+
try {
|
370
|
+
const tokensWithSameSymbol = await apiDbClient.token.findMany({
|
371
|
+
select: { chainId: true, address: true },
|
372
|
+
where: { symbol: { equals: symbol, mode: "insensitive" } },
|
373
|
+
});
|
374
|
+
for (const dbToken of tokensWithSameSymbol) {
|
375
|
+
await apiDbClient.token.update({
|
376
|
+
data: { icon: token.logoURI },
|
377
|
+
where: {
|
378
|
+
chainId_address: {
|
379
|
+
chainId: dbToken.chainId,
|
380
|
+
address: dbToken.address,
|
381
|
+
},
|
382
|
+
},
|
383
|
+
});
|
384
|
+
}
|
385
|
+
}
|
386
|
+
catch (_err) { }
|
387
|
+
}
|
388
|
+
}
|
389
|
+
}
|
378
390
|
}
|
@@ -19,7 +19,7 @@ export default (app) => app.get("/app", async () => {
|
|
19
19
|
if (!json.tokens[token.chainId]) {
|
20
20
|
json.tokens[token.chainId] = {};
|
21
21
|
}
|
22
|
-
if (!json.tokens[token.chainId][token.address]) {
|
22
|
+
if (!json.tokens[token.chainId][token.address] && token.icon?.length > 0) {
|
23
23
|
json.tokens[token.chainId][token.address] = {
|
24
24
|
address: token.address,
|
25
25
|
chainId: token.chainId,
|
@@ -26,7 +26,7 @@ export default class PriceService {
|
|
26
26
|
});
|
27
27
|
};
|
28
28
|
async fetchPrices() {
|
29
|
-
const tokenPriceSources = await PriceSourceService.
|
29
|
+
const tokenPriceSources = await PriceSourceService.findManyPriceSources();
|
30
30
|
/**
|
31
31
|
* @description Factory pricer's call to get prices from different sources
|
32
32
|
*/
|