otomato-sdk 2.0.650 → 2.0.652
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/constants/Blocks.js +2 -2
- package/dist/src/constants/version.js +1 -1
- package/dist/src/utils/helpers.js +157 -30
- package/dist/types/src/constants/version.d.ts +1 -1
- package/dist/types/src/utils/helpers.d.ts +40 -3
- package/dist/types/test/tokenPrice.spec.d.ts +1 -0
- package/package.json +1 -1
|
@@ -18604,11 +18604,11 @@ export const ACTIONS = {
|
|
|
18604
18604
|
"examples": [
|
|
18605
18605
|
{
|
|
18606
18606
|
"name": "WETH price on Base",
|
|
18607
|
-
"description": "Get WETH price on Base via
|
|
18607
|
+
"description": "Get WETH price on Base via DeFi Llama",
|
|
18608
18608
|
"parameters": [
|
|
18609
18609
|
{
|
|
18610
18610
|
"key": "url",
|
|
18611
|
-
"value": "https://
|
|
18611
|
+
"value": "https://coins.llama.fi/prices/current/base:0x4200000000000000000000000000000000000006"
|
|
18612
18612
|
},
|
|
18613
18613
|
{
|
|
18614
18614
|
"key": "method",
|
|
@@ -137,40 +137,167 @@ export function formatNonZeroDecimals(value, nonZeroDecimals = 2, maxLeadingZero
|
|
|
137
137
|
}
|
|
138
138
|
return sign + result;
|
|
139
139
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
140
|
+
/**
|
|
141
|
+
* #3032 — token pricing.
|
|
142
|
+
*
|
|
143
|
+
* `api.odos.xyz` was shut down on 2026-07-30 and the whole host now serves a
|
|
144
|
+
* Cloudflare error page (HTTP 530, `error code: 1033`). Because the old code did
|
|
145
|
+
* `response.json()` on it, the failure surfaced as a JSON *parse* error
|
|
146
|
+
* (`Unexpected token '<', "<!doctype "...`) thrown out of every caller, not as an
|
|
147
|
+
* HTTP error — so block `after`/`handler` functions blew up instead of degrading.
|
|
148
|
+
*
|
|
149
|
+
* Prices now come from DeFi Llama's keyless coins API, which is the same upstream
|
|
150
|
+
* our own price store admits from (`source: "defillama"`), covers every chain in
|
|
151
|
+
* CHAINS, and prices arbitrary tokens on demand — including unclaimed reward
|
|
152
|
+
* tokens the holdings-driven internal store can never admit.
|
|
153
|
+
*
|
|
154
|
+
* li.quest is kept as a fallback for HyperEVM so the one path that still worked
|
|
155
|
+
* before this change cannot regress.
|
|
156
|
+
*
|
|
157
|
+
* These functions are deliberately STATELESS. The SDK is a public npm package with
|
|
158
|
+
* no Redis and no DI container, and an in-process cache would be per-instance —
|
|
159
|
+
* the very thing webserver's priceStoreService warns against ("no in-memory maps
|
|
160
|
+
* per the two-instance rule"). Server-side callers must go through sharedlibs
|
|
161
|
+
* `WalletService.getCachedPrice`, which wraps these in the shared Redis cache
|
|
162
|
+
* under the `price:{chainId}:{address}:{currency}` key.
|
|
163
|
+
*/
|
|
164
|
+
/** chainId -> DeFi Llama coins-API chain slug. Every entry verified live against a real token. */
|
|
165
|
+
export const CHAIN_ID_TO_LLAMA_SLUG = {
|
|
166
|
+
[CHAINS.ETHEREUM]: 'ethereum',
|
|
167
|
+
[CHAINS.OPTIMISM]: 'optimism',
|
|
168
|
+
[CHAINS.BINANCE]: 'bsc',
|
|
169
|
+
[CHAINS.POLYGON]: 'polygon',
|
|
170
|
+
[CHAINS.MONAD]: 'monad',
|
|
171
|
+
[CHAINS.SONIC]: 'sonic',
|
|
172
|
+
[CHAINS.HYPER_EVM]: 'hyperliquid',
|
|
173
|
+
[CHAINS.ABSTRACT]: 'abstract',
|
|
174
|
+
[CHAINS.MANTLE]: 'mantle',
|
|
175
|
+
[CHAINS.SOMNIA]: 'somnia',
|
|
176
|
+
[CHAINS.BASE]: 'base',
|
|
177
|
+
[CHAINS.PLASMA]: 'plasma',
|
|
178
|
+
[CHAINS.OASIS]: 'sapphire',
|
|
179
|
+
[CHAINS.MODE]: 'mode',
|
|
180
|
+
[CHAINS.ARBITRUM]: 'arbitrum',
|
|
181
|
+
[CHAINS.AVALANCHE]: 'avax',
|
|
182
|
+
[CHAINS.INK]: 'ink',
|
|
183
|
+
[CHAINS.SCROLL]: 'scroll',
|
|
184
|
+
// #3038 added Robinhood Chain to CHAINS after #3032 was written. Slug probed 2026-09-08:
|
|
185
|
+
// 'robinhood' prices GOOGL 0x2e0847E8... AND USDG 0x5fc5360D...; 'robinhoodchain' is a
|
|
186
|
+
// partial alias returning USDG only. Native 0x0 resolves to ETH on BOTH, so the native
|
|
187
|
+
// sentinel alone does not disambiguate them - always probe a real ERC20.
|
|
188
|
+
[CHAINS.ROBINHOOD]: 'robinhood',
|
|
189
|
+
};
|
|
190
|
+
const LLAMA_PRICES_URL = 'https://coins.llama.fi/prices/current/';
|
|
191
|
+
const PRICE_TIMEOUT_MS = 8_000;
|
|
192
|
+
/** Keep each request URL well inside any gateway limit. */
|
|
193
|
+
const LLAMA_BATCH_SIZE = 40;
|
|
194
|
+
const chunk = (items, size) => {
|
|
195
|
+
const out = [];
|
|
196
|
+
for (let i = 0; i < items.length; i += size)
|
|
197
|
+
out.push(items.slice(i, i + size));
|
|
198
|
+
return out;
|
|
199
|
+
};
|
|
200
|
+
const fetchJson = async (url, init) => {
|
|
201
|
+
try {
|
|
202
|
+
const response = await fetch(url, { ...(init ?? {}), signal: AbortSignal.timeout(PRICE_TIMEOUT_MS) });
|
|
203
|
+
if (!response.ok)
|
|
204
|
+
return null;
|
|
205
|
+
return await response.json();
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// A dead host, a timeout, or an HTML error page must degrade to "no price",
|
|
209
|
+
// never throw into the caller. See the #3032 note above.
|
|
210
|
+
return null;
|
|
150
211
|
}
|
|
151
|
-
const tokenPrice = await fetch(`https://api.odos.xyz/pricing/token/${chainId}/${contractAddress}`);
|
|
152
|
-
const tokenPriceJson = await tokenPrice.json();
|
|
153
|
-
return tokenPriceJson?.price;
|
|
154
212
|
};
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
213
|
+
/** Look tokens up on DeFi Llama. Unknown tokens are simply absent from the result. */
|
|
214
|
+
const fetchFromLlama = async (chainId, contractAddresses) => {
|
|
215
|
+
const found = new Map();
|
|
216
|
+
const slug = CHAIN_ID_TO_LLAMA_SLUG[chainId];
|
|
217
|
+
if (!slug || contractAddresses.length === 0)
|
|
218
|
+
return found;
|
|
219
|
+
for (const batch of chunk(contractAddresses, LLAMA_BATCH_SIZE)) {
|
|
220
|
+
const json = await fetchJson(LLAMA_PRICES_URL + batch.map((a) => `${slug}:${a}`).join(','));
|
|
221
|
+
const coins = json?.coins ?? {};
|
|
222
|
+
for (const address of batch) {
|
|
223
|
+
const coin = coins[`${slug}:${address}`];
|
|
224
|
+
const price = Number(coin?.price);
|
|
225
|
+
if (!Number.isFinite(price) || price <= 0)
|
|
226
|
+
continue;
|
|
227
|
+
found.set(address, {
|
|
228
|
+
contractAddress: address,
|
|
229
|
+
symbol: coin.symbol,
|
|
230
|
+
decimals: Number(coin.decimals),
|
|
231
|
+
priceUSD: price,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return found;
|
|
236
|
+
};
|
|
237
|
+
/** HyperEVM-only fallback: the source that kept working while Odos was down. */
|
|
238
|
+
const fetchFromLifi = async (contractAddresses) => {
|
|
239
|
+
const found = new Map();
|
|
240
|
+
if (contractAddresses.length === 0)
|
|
241
|
+
return found;
|
|
242
|
+
const json = await fetchJson(`https://li.quest/v1/tokens?chains=${CHAINS.HYPER_EVM}`, {
|
|
243
|
+
headers: { 'x-lifi-api-key': process.env.LIFI_API_KEY ?? '' },
|
|
244
|
+
});
|
|
245
|
+
const tokens = json?.tokens?.[CHAINS.HYPER_EVM] ?? [];
|
|
246
|
+
const byAddress = new Map(tokens.map((t) => [String(t.address).toLowerCase(), t]));
|
|
247
|
+
for (const address of contractAddresses) {
|
|
248
|
+
const token = byAddress.get(String(address).toLowerCase());
|
|
249
|
+
const price = Number(token?.priceUSD);
|
|
250
|
+
if (!Number.isFinite(price) || price <= 0)
|
|
251
|
+
continue;
|
|
252
|
+
found.set(address, {
|
|
253
|
+
contractAddress: address,
|
|
254
|
+
symbol: token.symbol,
|
|
255
|
+
decimals: Number(token.decimals),
|
|
256
|
+
priceUSD: price,
|
|
171
257
|
});
|
|
172
258
|
}
|
|
173
|
-
return
|
|
259
|
+
return found;
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Resolve USD prices for a list of tokens on one chain.
|
|
263
|
+
* Never throws — unresolved tokens are simply omitted from the result.
|
|
264
|
+
*/
|
|
265
|
+
const resolvePrices = async (chainId, contractAddresses) => {
|
|
266
|
+
const resolved = new Map();
|
|
267
|
+
const missing = [];
|
|
268
|
+
for (const address of contractAddresses) {
|
|
269
|
+
if (address && !missing.includes(address))
|
|
270
|
+
missing.push(address);
|
|
271
|
+
}
|
|
272
|
+
if (missing.length === 0)
|
|
273
|
+
return resolved;
|
|
274
|
+
const fromLlama = await fetchFromLlama(chainId, missing);
|
|
275
|
+
for (const [address, priced] of fromLlama)
|
|
276
|
+
resolved.set(address, priced);
|
|
277
|
+
const stillMissing = missing.filter((a) => !fromLlama.has(a));
|
|
278
|
+
if (stillMissing.length > 0 && chainId === CHAINS.HYPER_EVM) {
|
|
279
|
+
const fromLifi = await fetchFromLifi(stillMissing);
|
|
280
|
+
for (const [address, priced] of fromLifi)
|
|
281
|
+
resolved.set(address, priced);
|
|
282
|
+
}
|
|
283
|
+
return resolved;
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
286
|
+
* USD price of one token, or `null` when it cannot be resolved.
|
|
287
|
+
* Callers already treat `null`/`undefined` as "no price"; this function never throws.
|
|
288
|
+
* Uncached — server-side callers should use sharedlibs `WalletService.getCachedPrice`.
|
|
289
|
+
*/
|
|
290
|
+
export const getTokenPrice = async (chainId, contractAddress) => {
|
|
291
|
+
const resolved = await resolvePrices(chainId, [contractAddress]);
|
|
292
|
+
return resolved.get(contractAddress)?.priceUSD ?? null;
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* USD prices for many tokens on one chain, in a single upstream request.
|
|
296
|
+
* Tokens that cannot be resolved are omitted, so the result may be shorter than the input.
|
|
297
|
+
*/
|
|
298
|
+
export const getTokenPrices = async (chainId, contractAddresses) => {
|
|
299
|
+
const resolved = await resolvePrices(chainId, contractAddresses);
|
|
300
|
+
return contractAddresses.map((a) => resolved.get(a)).filter((p) => Boolean(p));
|
|
174
301
|
};
|
|
175
302
|
export const getETHAlternativeTokensSymbols = () => {
|
|
176
303
|
return {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "2.0.
|
|
1
|
+
export declare const SDK_VERSION = "2.0.652";
|
|
2
2
|
export declare function compareVersions(v1: string, v2: string): number;
|
|
@@ -35,13 +35,49 @@ export declare function getComputeERC20Variable(amount: string, chainId: any, co
|
|
|
35
35
|
* @returns string - The formatted number as a string.
|
|
36
36
|
*/
|
|
37
37
|
export declare function formatNonZeroDecimals(value: number | bigint, nonZeroDecimals?: number, maxLeadingZeros?: number): string;
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
/**
|
|
39
|
+
* #3032 — token pricing.
|
|
40
|
+
*
|
|
41
|
+
* `api.odos.xyz` was shut down on 2026-07-30 and the whole host now serves a
|
|
42
|
+
* Cloudflare error page (HTTP 530, `error code: 1033`). Because the old code did
|
|
43
|
+
* `response.json()` on it, the failure surfaced as a JSON *parse* error
|
|
44
|
+
* (`Unexpected token '<', "<!doctype "...`) thrown out of every caller, not as an
|
|
45
|
+
* HTTP error — so block `after`/`handler` functions blew up instead of degrading.
|
|
46
|
+
*
|
|
47
|
+
* Prices now come from DeFi Llama's keyless coins API, which is the same upstream
|
|
48
|
+
* our own price store admits from (`source: "defillama"`), covers every chain in
|
|
49
|
+
* CHAINS, and prices arbitrary tokens on demand — including unclaimed reward
|
|
50
|
+
* tokens the holdings-driven internal store can never admit.
|
|
51
|
+
*
|
|
52
|
+
* li.quest is kept as a fallback for HyperEVM so the one path that still worked
|
|
53
|
+
* before this change cannot regress.
|
|
54
|
+
*
|
|
55
|
+
* These functions are deliberately STATELESS. The SDK is a public npm package with
|
|
56
|
+
* no Redis and no DI container, and an in-process cache would be per-instance —
|
|
57
|
+
* the very thing webserver's priceStoreService warns against ("no in-memory maps
|
|
58
|
+
* per the two-instance rule"). Server-side callers must go through sharedlibs
|
|
59
|
+
* `WalletService.getCachedPrice`, which wraps these in the shared Redis cache
|
|
60
|
+
* under the `price:{chainId}:{address}:{currency}` key.
|
|
61
|
+
*/
|
|
62
|
+
/** chainId -> DeFi Llama coins-API chain slug. Every entry verified live against a real token. */
|
|
63
|
+
export declare const CHAIN_ID_TO_LLAMA_SLUG: Record<number, string>;
|
|
64
|
+
interface PricedToken {
|
|
40
65
|
contractAddress: string;
|
|
41
66
|
symbol: string;
|
|
42
67
|
decimals: number;
|
|
43
68
|
priceUSD: number;
|
|
44
|
-
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* USD price of one token, or `null` when it cannot be resolved.
|
|
72
|
+
* Callers already treat `null`/`undefined` as "no price"; this function never throws.
|
|
73
|
+
* Uncached — server-side callers should use sharedlibs `WalletService.getCachedPrice`.
|
|
74
|
+
*/
|
|
75
|
+
export declare const getTokenPrice: (chainId: number, contractAddress: string) => Promise<number | null>;
|
|
76
|
+
/**
|
|
77
|
+
* USD prices for many tokens on one chain, in a single upstream request.
|
|
78
|
+
* Tokens that cannot be resolved are omitted, so the result may be shorter than the input.
|
|
79
|
+
*/
|
|
80
|
+
export declare const getTokenPrices: (chainId: number, contractAddresses: string[]) => Promise<PricedToken[]>;
|
|
45
81
|
export declare const getETHAlternativeTokensSymbols: () => {
|
|
46
82
|
1: string;
|
|
47
83
|
130: string;
|
|
@@ -260,3 +296,4 @@ export declare function isMaxBigInt(value: bigint | string): boolean;
|
|
|
260
296
|
* convention used by AAVE/Hyperlend/Hypurr/Moonwell handlers).
|
|
261
297
|
*/
|
|
262
298
|
export declare function formatHealthFactor(value: number | bigint | string): string | bigint;
|
|
299
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|