@defillama/sdk 5.0.235 → 5.0.236

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.
Files changed (60) hide show
  1. package/build/ChainApi.d.ts +23 -1
  2. package/build/chains/algorand.d.ts +277 -0
  3. package/build/chains/algorand.js +618 -0
  4. package/build/chains/aptos.d.ts +230 -0
  5. package/build/chains/aptos.js +612 -0
  6. package/build/chains/cardano.d.ts +242 -0
  7. package/build/chains/cardano.js +407 -0
  8. package/build/chains/cosmos.d.ts +252 -0
  9. package/build/chains/cosmos.js +618 -0
  10. package/build/chains/icp.d.ts +235 -0
  11. package/build/chains/icp.js +1040 -0
  12. package/build/chains/index.d.ts +23 -0
  13. package/build/chains/index.js +59 -0
  14. package/build/chains/near.d.ts +192 -0
  15. package/build/chains/near.js +446 -0
  16. package/build/chains/rpc.d.ts +125 -0
  17. package/build/chains/rpc.js +326 -0
  18. package/build/chains/starknet.d.ts +205 -0
  19. package/build/chains/starknet.js +884 -0
  20. package/build/chains/stellar.d.ts +410 -0
  21. package/build/chains/stellar.js +1082 -0
  22. package/build/chains/substrate.d.ts +333 -0
  23. package/build/chains/substrate.js +1042 -0
  24. package/build/chains/sui.d.ts +365 -0
  25. package/build/chains/sui.js +1135 -0
  26. package/build/chains/svm.d.ts +340 -0
  27. package/build/chains/svm.js +760 -0
  28. package/build/chains/tezos.d.ts +225 -0
  29. package/build/chains/tezos.js +412 -0
  30. package/build/chains/ton.d.ts +353 -0
  31. package/build/chains/ton.js +788 -0
  32. package/build/chains/tron.d.ts +45 -0
  33. package/build/chains/tron.js +110 -0
  34. package/build/chains/utxo.d.ts +202 -0
  35. package/build/chains/utxo.js +592 -0
  36. package/build/chains/xrpl.d.ts +179 -0
  37. package/build/chains/xrpl.js +467 -0
  38. package/build/index.d.ts +2 -1
  39. package/build/index.js +3 -1
  40. package/build/providers.json +1 -1
  41. package/build/util/chainUtils/data.json +11 -0
  42. package/build/util/chainUtils/index.d.ts +1 -0
  43. package/build/util/chainUtils/index.js +23 -0
  44. package/build/util/common.js +2 -3
  45. package/build/util/cosmos.d.ts +1 -11
  46. package/build/util/cosmos.js +6 -66
  47. package/build/util/env.js +1 -0
  48. package/build/util/indexer.compatibility.d.ts +6 -0
  49. package/build/util/indexer.compatibility.js +64 -0
  50. package/build/util/indexer.d.ts +24 -2
  51. package/build/util/indexer.js +215 -282
  52. package/build/util/indexerSql.d.ts +8 -1
  53. package/build/util/indexerSql.js +53 -22
  54. package/build/util/logs.decode.shared.js +5 -4
  55. package/build/util/transactions.d.ts +23 -1
  56. package/build/util/updateChainLabels.d.ts +1 -0
  57. package/build/util/updateChainLabels.js +5 -0
  58. package/build/util/updateProviderList.js +38 -17
  59. package/package.json +20 -7
  60. package/README.md +0 -41
@@ -0,0 +1,618 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_ALGOD = exports.DEFAULT_INDEXER = void 0;
4
+ exports.getIndexerEndpoints = getIndexerEndpoints;
5
+ exports.getIndexerEndpoint = getIndexerEndpoint;
6
+ exports.getAlgodEndpoints = getAlgodEndpoints;
7
+ exports.getAlgodEndpoint = getAlgodEndpoint;
8
+ exports.parseJsonSafe = parseJsonSafe;
9
+ exports.indexerGet = indexerGet;
10
+ exports.algodGet = algodGet;
11
+ exports.lookupApplication = lookupApplication;
12
+ exports.lookupAccount = lookupAccount;
13
+ exports.getAccountInfo = getAccountInfo;
14
+ exports.clearAccountInfoCache = clearAccountInfoCache;
15
+ exports.searchAccounts = searchAccounts;
16
+ exports.searchAccountsAll = searchAccountsAll;
17
+ exports.lookupApplicationsCreatedByAccount = lookupApplicationsCreatedByAccount;
18
+ exports.getAssetInfo = getAssetInfo;
19
+ exports.getAssetBalance = getAssetBalance;
20
+ exports.getAlgoBalance = getAlgoBalance;
21
+ exports.getAssetSupply = getAssetSupply;
22
+ exports.decodeGlobalStateKey = decodeGlobalStateKey;
23
+ exports.decodeGlobalStateValue = decodeGlobalStateValue;
24
+ exports.decodeGlobalState = decodeGlobalState;
25
+ exports.getAppGlobalState = getAppGlobalState;
26
+ exports.bytesAsAddress = bytesAsAddress;
27
+ exports.bytesAsUint64s = bytesAsUint64s;
28
+ exports.bytesAsUtf8 = bytesAsUtf8;
29
+ exports.getApplicationBoxes = getApplicationBoxes;
30
+ exports.getApplicationBoxesAll = getApplicationBoxesAll;
31
+ exports.encodeBoxName = encodeBoxName;
32
+ exports.getApplicationBox = getApplicationBox;
33
+ exports.lookupTransactions = lookupTransactions;
34
+ exports.getBlock = getBlock;
35
+ exports.getLatestBlock = getLatestBlock;
36
+ exports.getBlockAtTimestamp = getBlockAtTimestamp;
37
+ exports.base32Encode = base32Encode;
38
+ exports.base32Decode = base32Decode;
39
+ exports.sha512_256 = sha512_256;
40
+ exports.concatArrays = concatArrays;
41
+ exports.encodeAddress = encodeAddress;
42
+ exports.decodeAddress = decodeAddress;
43
+ exports.isValidAddress = isValidAddress;
44
+ exports.encodeUint64 = encodeUint64;
45
+ exports.getApplicationAddress = getApplicationAddress;
46
+ /**
47
+ * Algorand indexer / algod client plus the address codec.
48
+ *
49
+ * Replaces (and consolidates) these per-repo copies:
50
+ * - DefiLlama-Adapters projects/helper/chain/algorand.js (client, lookups, asset/account/global-state helpers, boxes)
51
+ * - DefiLlama-Adapters projects/helper/chain/algorandUtils/address.js (encodeAddress, getApplicationAddress, encodeUint64, genericHash)
52
+ * - peggedassets-server src/adapters/peggedAssets/helper/algorand.js and helper/getSupply.ts (algorandGetAssetParams / TotalSupply / Balance)
53
+ * - coins server src/scripts/coingeckoUtils.ts (algorand asset symbol / decimals lookup)
54
+ * - dimension-adapters fees/folks-finance.ts (global state key / uint64 decoding idiom)
55
+ *
56
+ * Endpoints: `ALGORAND_INDEXER` (default https://mainnet-idx.algonode.cloud) and `ALGORAND_RPC` for algod
57
+ * (default https://mainnet-api.algonode.cloud), both comma separated lists with rotation on failure.
58
+ * Every request goes through the shared `algorand` limiter (10 concurrent, `ALGORAND_RPC_CONCURRENCY` overrides).
59
+ *
60
+ * Responses are parsed with a uint64-safe JSON parser: integer literals that do not fit in a JS number
61
+ * (asset totals such as 2^64-1, large 0-decimal amounts) are returned as decimal strings instead of
62
+ * being rounded. Everything that is documented as returning a string amount is normalised with `String()`.
63
+ *
64
+ * Deliberately not ported: `sumTokens`, `resolveTinymanLp`, `getPriceFromAlgoFiLP` and the `tokens` id map
65
+ * (TVL bookkeeping that belongs to the adapters repo).
66
+ */
67
+ const crypto_1 = require("crypto");
68
+ const rpc_1 = require("./rpc");
69
+ const debugLog_1 = require("../util/debugLog");
70
+ // ---------------------------------------------------------------------------
71
+ // config
72
+ // ---------------------------------------------------------------------------
73
+ exports.DEFAULT_INDEXER = 'https://mainnet-idx.algonode.cloud';
74
+ exports.DEFAULT_ALGOD = 'https://mainnet-api.algonode.cloud';
75
+ const INDEXER_ENV_KEY = 'ALGORAND_INDEXER';
76
+ const LIMITER_KEY = 'algorand';
77
+ const LIMITER_CONCURRENCY = 10;
78
+ /** Indexer endpoint list: `ALGORAND_INDEXER` env (comma separated) or the default. */
79
+ function getIndexerEndpoints() {
80
+ return (0, rpc_1.getEndpoints)('algorand', exports.DEFAULT_INDEXER, { envKey: INDEXER_ENV_KEY });
81
+ }
82
+ /** First indexer endpoint. */
83
+ function getIndexerEndpoint() {
84
+ return getIndexerEndpoints()[0];
85
+ }
86
+ /** Algod endpoint list: `ALGORAND_RPC` env (comma separated) or the default. */
87
+ function getAlgodEndpoints() {
88
+ return (0, rpc_1.getEndpoints)('algorand', exports.DEFAULT_ALGOD);
89
+ }
90
+ /** First algod endpoint. */
91
+ function getAlgodEndpoint() {
92
+ return getAlgodEndpoints()[0];
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // transport
96
+ // ---------------------------------------------------------------------------
97
+ function limiter() {
98
+ return (0, rpc_1.getLimiter)(LIMITER_KEY, LIMITER_CONCURRENCY);
99
+ }
100
+ /**
101
+ * JSON.parse that keeps integers too large for a JS number as decimal strings.
102
+ * Only integer literals outside of strings that fail `Number.isSafeInteger` are quoted.
103
+ */
104
+ function parseJsonSafe(text) {
105
+ let out = '';
106
+ let last = 0;
107
+ let inString = false;
108
+ const n = text.length;
109
+ let i = 0;
110
+ while (i < n) {
111
+ const ch = text[i];
112
+ if (inString) {
113
+ if (ch === '\\') {
114
+ i += 2;
115
+ continue;
116
+ }
117
+ if (ch === '"')
118
+ inString = false;
119
+ i++;
120
+ continue;
121
+ }
122
+ if (ch === '"') {
123
+ inString = true;
124
+ i++;
125
+ continue;
126
+ }
127
+ if ((ch >= '0' && ch <= '9') || ch === '-') {
128
+ let j = i;
129
+ if (text[j] === '-')
130
+ j++;
131
+ const start = j;
132
+ while (j < n && text[j] >= '0' && text[j] <= '9')
133
+ j++;
134
+ const next = text[j];
135
+ const isInteger = next !== '.' && next !== 'e' && next !== 'E';
136
+ const digits = j - start;
137
+ if (isInteger && digits > 15 && !Number.isSafeInteger(Number(text.slice(i, j)))) {
138
+ out += text.slice(last, i) + '"' + text.slice(i, j) + '"';
139
+ last = j;
140
+ }
141
+ i = Math.max(j, i + 1);
142
+ continue;
143
+ }
144
+ i++;
145
+ }
146
+ return JSON.parse(last ? out + text.slice(last) : text);
147
+ }
148
+ function stripUndefined(params) {
149
+ if (!params)
150
+ return undefined;
151
+ const res = {};
152
+ Object.keys(params).forEach(k => { if (params[k] !== undefined && params[k] !== null)
153
+ res[k] = params[k]; });
154
+ return res;
155
+ }
156
+ async function getJson(urls, { path, params, allow404 }) {
157
+ return limiter()(async () => {
158
+ const res = await (0, rpc_1.httpGet)(urls, {
159
+ path,
160
+ params: stripUndefined(params),
161
+ withMetadata: true,
162
+ axiosConfig: {
163
+ transformResponse: [(data) => data],
164
+ validateStatus: (status) => (status >= 200 && status < 300) || (!!allow404 && status === 404),
165
+ },
166
+ });
167
+ if (res.status === 404) {
168
+ (0, debugLog_1.debugLog)(`[chains.algorand] 404 ${path}`);
169
+ return undefined;
170
+ }
171
+ const data = res.data;
172
+ if (typeof data !== 'string')
173
+ return data;
174
+ if (!data)
175
+ return undefined;
176
+ return parseJsonSafe(data);
177
+ });
178
+ }
179
+ /** Raw GET against the indexer (`/v2/...`), parsed with the uint64-safe parser. */
180
+ async function indexerGet({ path, params }) {
181
+ return getJson(getIndexerEndpoints(), { path, params });
182
+ }
183
+ /** Raw GET against algod (`/v2/...`), parsed with the uint64-safe parser. */
184
+ async function algodGet({ path, params }) {
185
+ return getJson(getAlgodEndpoints(), { path, params });
186
+ }
187
+ // ---------------------------------------------------------------------------
188
+ // applications / accounts
189
+ // ---------------------------------------------------------------------------
190
+ /** `/v2/applications/{appId}` -> the `application` object (id, params, global-state, ...) */
191
+ async function lookupApplication({ appId }) {
192
+ const res = await indexerGet({ path: `/v2/applications/${appId}` });
193
+ return res.application;
194
+ }
195
+ /**
196
+ * `/v2/accounts/{address}` -> the `account` object. `includeAll` also returns closed-out / deleted holdings.
197
+ * Resolves to `undefined` when the indexer has never seen the account (404).
198
+ */
199
+ async function lookupAccount({ address, includeAll, params }) {
200
+ const query = { ...params, 'include-all': includeAll ? true : undefined };
201
+ try {
202
+ const res = await getJson(getIndexerEndpoints(), { path: `/v2/accounts/${address}`, params: query, allow404: true, });
203
+ return res === null || res === void 0 ? void 0 : res.account;
204
+ }
205
+ catch (e) {
206
+ // accounts with > 10k created assets/apps or local states are rejected with 400 "max-results";
207
+ // retry without the large collections so holdings (`assets`) are still returned
208
+ if (query.exclude || !isMaxResultsError(e))
209
+ throw e;
210
+ (0, debugLog_1.debugLog)(`[chains.algorand] account ${address} exceeds max-results, retrying with exclude`);
211
+ try {
212
+ const res = await getJson(getIndexerEndpoints(), {
213
+ path: `/v2/accounts/${address}`,
214
+ params: { ...query, exclude: 'created-assets,created-apps,apps-local-state' },
215
+ allow404: true,
216
+ });
217
+ return res === null || res === void 0 ? void 0 : res.account;
218
+ }
219
+ catch (e2) {
220
+ if (!isMaxResultsError(e2))
221
+ throw e2;
222
+ // even the holdings are too many, return the bare account (amount, round, status...)
223
+ (0, debugLog_1.debugLog)(`[chains.algorand] account ${address} holdings exceed max-results, returning account without assets`);
224
+ const res = await getJson(getIndexerEndpoints(), { path: `/v2/accounts/${address}`, params: { ...query, exclude: 'all' }, allow404: true, });
225
+ return res === null || res === void 0 ? void 0 : res.account;
226
+ }
227
+ }
228
+ }
229
+ function isMaxResultsError(e) {
230
+ var _a;
231
+ return String((_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : '').includes('max-results');
232
+ }
233
+ const accountInfoCache = {};
234
+ /**
235
+ * Account with holdings indexed by asset id. Memoised per process (same address -> same promise), like the
236
+ * adapters helper it replaces. A numeric `address` is treated as an application id and resolved to its escrow address.
237
+ * The ALGO balance is added to `assets` / `assetMapping` under the pseudo asset id '1'.
238
+ */
239
+ function getAccountInfo({ address }) {
240
+ const key = typeof address === 'number' ? getApplicationAddress(address) : address;
241
+ if (!accountInfoCache[key]) {
242
+ accountInfoCache[key] = _getAccountInfo(key).catch((e) => {
243
+ delete accountInfoCache[key]; // do not cache failures
244
+ throw e;
245
+ });
246
+ }
247
+ return accountInfoCache[key];
248
+ }
249
+ async function _getAccountInfo(address) {
250
+ var _a;
251
+ const account = await lookupAccount({ address });
252
+ if (!account)
253
+ throw new Error(`[chains.algorand] account not found: ${address}`);
254
+ const assets = ((_a = account.assets) !== null && _a !== void 0 ? _a : []).map(i => ({ ...i, 'asset-id': String(i['asset-id']) }));
255
+ if (account.amount)
256
+ assets.push({ amount: account.amount, 'asset-id': '1' });
257
+ const assetMapping = {};
258
+ assets.forEach(i => { assetMapping[String(i['asset-id'])] = i; });
259
+ return { ...account, assets, assetMapping };
260
+ }
261
+ /** Drop the memoised `getAccountInfo` entries (all, or one address). */
262
+ function clearAccountInfoCache(address) {
263
+ if (address)
264
+ delete accountInfoCache[address];
265
+ else
266
+ Object.keys(accountInfoCache).forEach(k => delete accountInfoCache[k]);
267
+ }
268
+ /** `/v2/accounts` search, one page -> `{ accounts, 'next-token', 'current-round' }` */
269
+ async function searchAccounts({ appId, assetId, limit = 1000, nextToken, params }) {
270
+ return indexerGet({
271
+ path: '/v2/accounts',
272
+ params: {
273
+ ...params,
274
+ 'application-id': appId,
275
+ 'asset-id': assetId,
276
+ limit,
277
+ next: nextToken,
278
+ },
279
+ });
280
+ }
281
+ /** All pages of `searchAccounts`. `onPage` is called with every page as it arrives. */
282
+ async function searchAccountsAll({ appId, assetId, limit = 1000, params, onPage }) {
283
+ var _a;
284
+ const accounts = [];
285
+ let nextToken;
286
+ let page = 0;
287
+ do {
288
+ const res = await searchAccounts({ appId, assetId, limit, nextToken, params });
289
+ nextToken = res['next-token'];
290
+ const pageAccounts = (_a = res.accounts) !== null && _a !== void 0 ? _a : [];
291
+ accounts.push(...pageAccounts);
292
+ if (onPage)
293
+ await onPage(pageAccounts, page);
294
+ page++;
295
+ (0, debugLog_1.debugLog)(`[chains.algorand] searchAccountsAll app=${appId !== null && appId !== void 0 ? appId : '-'} asset=${assetId !== null && assetId !== void 0 ? assetId : '-'} page=${page} total=${accounts.length}`);
296
+ } while (nextToken);
297
+ return accounts;
298
+ }
299
+ /** `/v2/accounts/{address}/created-applications` -> `{ applications, 'next-token', ... }` */
300
+ async function lookupApplicationsCreatedByAccount({ address, params }) {
301
+ return indexerGet({ path: `/v2/accounts/${address}/created-applications`, params });
302
+ }
303
+ // ---------------------------------------------------------------------------
304
+ // assets / balances
305
+ // ---------------------------------------------------------------------------
306
+ /** `/v2/assets/{assetId}` -> asset params (total, decimals, reserve, unit-name, ...) flattened with the asset metadata. */
307
+ async function getAssetInfo({ assetId }) {
308
+ const res = await indexerGet({ path: `/v2/assets/${assetId}` });
309
+ const asset = res.asset;
310
+ return { ...asset.params, ...asset };
311
+ }
312
+ /** Amount of `assetId` held by `address` as a base-unit string; '0' when the account is not opted in or unknown. */
313
+ async function getAssetBalance({ address, assetId }) {
314
+ var _a;
315
+ const res = await getJson(getIndexerEndpoints(), {
316
+ path: `/v2/accounts/${address}/assets`,
317
+ params: { 'asset-id': assetId },
318
+ allow404: true,
319
+ });
320
+ const holding = ((_a = res === null || res === void 0 ? void 0 : res.assets) !== null && _a !== void 0 ? _a : []).find((i) => String(i['asset-id']) === String(assetId));
321
+ return holding ? String(holding.amount) : '0';
322
+ }
323
+ /** Native ALGO balance of `address` in microalgos as a string; '0' for unknown accounts. */
324
+ async function getAlgoBalance({ address }) {
325
+ var _a;
326
+ const account = await lookupAccount({ address, params: { exclude: 'all' } });
327
+ return account ? String((_a = account.amount) !== null && _a !== void 0 ? _a : 0) : '0';
328
+ }
329
+ /**
330
+ * Supply figures for an ASA, all in base units. `circulating` is `total` minus the reserve account's holding,
331
+ * which is how Algorand issuers (USDC, USDt, ...) signal the un-issued part of a fixed total.
332
+ */
333
+ async function getAssetSupply({ assetId }) {
334
+ var _a;
335
+ const info = await getAssetInfo({ assetId });
336
+ const total = String(info.total);
337
+ const reserve = info.reserve || undefined;
338
+ let circulating = total;
339
+ if (reserve) {
340
+ const reserveHolding = await getAssetBalance({ address: reserve, assetId });
341
+ const diff = BigInt(total) - BigInt(reserveHolding);
342
+ circulating = (diff < BigInt(0) ? BigInt(0) : diff).toString();
343
+ }
344
+ return { total, decimals: Number((_a = info.decimals) !== null && _a !== void 0 ? _a : 0), reserve, circulating };
345
+ }
346
+ // ---------------------------------------------------------------------------
347
+ // global state
348
+ // ---------------------------------------------------------------------------
349
+ /** Global state key: base64 -> utf8 string. */
350
+ function decodeGlobalStateKey(b64) {
351
+ return Buffer.from(b64, 'base64').toString('utf8');
352
+ }
353
+ /**
354
+ * Global state value: uint (type 2) -> number (or decimal string when above 2^53), bytes (type 1) -> base64
355
+ * string as delivered by the indexer. Use `bytesAsAddress` / `bytesAsUint64s` / `bytesAsUtf8` to interpret bytes.
356
+ */
357
+ function decodeGlobalStateValue(value) {
358
+ var _a, _b;
359
+ if (value.type === 1)
360
+ return (_a = value.bytes) !== null && _a !== void 0 ? _a : '';
361
+ const uint = (_b = value.uint) !== null && _b !== void 0 ? _b : 0;
362
+ if (typeof uint === 'string') {
363
+ const asNumber = Number(uint);
364
+ return Number.isSafeInteger(asNumber) ? asNumber : uint;
365
+ }
366
+ return uint;
367
+ }
368
+ /** Decode an indexer `global-state` (or `local-state`) key/value list into a plain object. */
369
+ function decodeGlobalState(entries = []) {
370
+ const res = {};
371
+ entries.forEach(entry => { res[decodeGlobalStateKey(entry.key)] = decodeGlobalStateValue(entry.value); });
372
+ return res;
373
+ }
374
+ /** Decoded global state of an application: `{ [utf8 key]: number | string }`. */
375
+ async function getAppGlobalState({ appId }) {
376
+ var _a, _b;
377
+ const app = await lookupApplication({ appId });
378
+ return decodeGlobalState((_b = (_a = app === null || app === void 0 ? void 0 : app.params) === null || _a === void 0 ? void 0 : _a['global-state']) !== null && _b !== void 0 ? _b : []);
379
+ }
380
+ /** Interpret a 32 byte base64 state value as an Algorand address. */
381
+ function bytesAsAddress(b64) {
382
+ const bytes = Buffer.from(b64, 'base64');
383
+ if (bytes.length !== PUBLIC_KEY_LENGTH)
384
+ throw new Error(`[chains.algorand] expected 32 bytes for an address, got ${bytes.length}`);
385
+ return encodeAddress(new Uint8Array(bytes));
386
+ }
387
+ /** Interpret a base64 state value as consecutive big-endian uint64s (packed structs used by Folks, Tinyman, ...). */
388
+ function bytesAsUint64s(b64) {
389
+ const buf = Buffer.from(b64, 'base64');
390
+ const out = [];
391
+ for (let i = 0; i + 8 <= buf.length; i += 8)
392
+ out.push(buf.readBigUInt64BE(i));
393
+ return out;
394
+ }
395
+ /** Interpret a base64 state value as utf8 text. */
396
+ function bytesAsUtf8(b64) {
397
+ return Buffer.from(b64, 'base64').toString('utf8');
398
+ }
399
+ // ---------------------------------------------------------------------------
400
+ // boxes / transactions
401
+ // ---------------------------------------------------------------------------
402
+ /** `/v2/applications/{appId}/boxes`, one page -> `{ boxes: [{ name }], 'next-token' }` */
403
+ async function getApplicationBoxes({ appId, limit = 1000, nextToken }) {
404
+ return indexerGet({ path: `/v2/applications/${appId}/boxes`, params: { limit, next: nextToken } });
405
+ }
406
+ /** All box names of an application (base64 encoded). */
407
+ async function getApplicationBoxesAll({ appId, limit = 1000 }) {
408
+ var _a;
409
+ const boxes = [];
410
+ let nextToken;
411
+ do {
412
+ const res = await getApplicationBoxes({ appId, limit, nextToken });
413
+ nextToken = res['next-token'];
414
+ boxes.push(...((_a = res.boxes) !== null && _a !== void 0 ? _a : []));
415
+ } while (nextToken);
416
+ return boxes;
417
+ }
418
+ /**
419
+ * Encode a box name for the `name` query param. Strings already prefixed with `b64:` / `str:` / `int:` are passed
420
+ * through, raw bytes are base64 encoded, plain strings are sent as `str:`.
421
+ */
422
+ function encodeBoxName(name) {
423
+ if (typeof name !== 'string')
424
+ return 'b64:' + Buffer.from(name).toString('base64');
425
+ if (/^(b64|str|int):/.test(name))
426
+ return name;
427
+ return 'str:' + name;
428
+ }
429
+ /** `/v2/applications/{appId}/box?name=...` -> `{ name, value, round }` (base64 encoded name/value). */
430
+ async function getApplicationBox({ appId, name }) {
431
+ return indexerGet({ path: `/v2/applications/${appId}/box`, params: { name: encodeBoxName(name) } });
432
+ }
433
+ /** `/v2/transactions` search with raw indexer params (`address`, `asset-id`, `min-round`, `after-time`, `next`, ...). */
434
+ async function lookupTransactions({ params = {} } = {}) {
435
+ return indexerGet({ path: '/v2/transactions', params });
436
+ }
437
+ // ---------------------------------------------------------------------------
438
+ // blocks
439
+ // ---------------------------------------------------------------------------
440
+ /** `/v2/blocks/{round}` -> block header (round, timestamp, transactions, ...) */
441
+ async function getBlock({ round }) {
442
+ return indexerGet({ path: `/v2/blocks/${round}`, params: { 'header-only': true } });
443
+ }
444
+ /** Latest round the indexer has ingested (from `/health`) with its timestamp. */
445
+ async function getLatestBlock() {
446
+ const health = await indexerGet({ path: '/health' });
447
+ const round = Number(health.round);
448
+ if (!round)
449
+ throw new Error('[chains.algorand] indexer /health returned no round');
450
+ const block = await getBlock({ round });
451
+ return { number: round, timestamp: Number(block.timestamp) };
452
+ }
453
+ /**
454
+ * First block whose timestamp is >= `timestamp` (unix seconds). Fast path: the first transaction after that time
455
+ * (indexer `after-time`); falls back to a binary search over block headers.
456
+ */
457
+ async function getBlockAtTimestamp({ timestamp }) {
458
+ var _a, _b, _c;
459
+ const latest = await getLatestBlock();
460
+ if (timestamp >= latest.timestamp)
461
+ return latest;
462
+ try {
463
+ const res = await lookupTransactions({ params: { 'after-time': new Date(timestamp * 1000).toISOString(), limit: 1 } });
464
+ const round = Number((_b = (_a = res === null || res === void 0 ? void 0 : res.transactions) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b['confirmed-round']);
465
+ if (round) {
466
+ const block = await getBlock({ round });
467
+ const blockTime = Number(block.timestamp);
468
+ if (blockTime >= timestamp) {
469
+ // make sure the previous block is really before the timestamp, otherwise refine with the binary search
470
+ const prev = round > 1 ? await getBlock({ round: round - 1 }) : undefined;
471
+ if (!prev || Number(prev.timestamp) < timestamp)
472
+ return { number: round, timestamp: blockTime };
473
+ }
474
+ }
475
+ }
476
+ catch (e) {
477
+ (0, debugLog_1.debugLog)(`[chains.algorand] getBlockAtTimestamp fast path failed: ${(_c = e === null || e === void 0 ? void 0 : e.message) !== null && _c !== void 0 ? _c : e}`);
478
+ }
479
+ let lo = 1;
480
+ let hi = latest.number;
481
+ let best = latest;
482
+ while (lo <= hi) {
483
+ const mid = Math.floor((lo + hi) / 2);
484
+ const block = await getBlock({ round: mid });
485
+ const blockTime = Number(block.timestamp);
486
+ if (blockTime >= timestamp) {
487
+ best = { number: mid, timestamp: blockTime };
488
+ hi = mid - 1;
489
+ }
490
+ else {
491
+ lo = mid + 1;
492
+ }
493
+ }
494
+ return best;
495
+ }
496
+ // ---------------------------------------------------------------------------
497
+ // codec: base32 (RFC 4648), sha512/256, addresses, uint64
498
+ // ---------------------------------------------------------------------------
499
+ const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
500
+ const BASE32_LOOKUP = {};
501
+ for (let i = 0; i < BASE32_ALPHABET.length; i++)
502
+ BASE32_LOOKUP[BASE32_ALPHABET[i]] = i;
503
+ const ALGORAND_CHECKSUM_BYTE_LENGTH = 4;
504
+ const ALGORAND_ADDRESS_LENGTH = 58;
505
+ const PUBLIC_KEY_LENGTH = 32;
506
+ const APP_ID_PREFIX = new Uint8Array(Buffer.from('appID'));
507
+ const MAX_UINT64 = BigInt('0xffffffffffffffff');
508
+ /** RFC 4648 base32 with `=` padding. */
509
+ function base32Encode(bytes) {
510
+ let out = '';
511
+ let bits = 0;
512
+ let value = 0;
513
+ for (let i = 0; i < bytes.length; i++) {
514
+ value = ((value << 8) | bytes[i]) & 0xffff;
515
+ bits += 8;
516
+ while (bits >= 5) {
517
+ out += BASE32_ALPHABET[(value >>> (bits - 5)) & 31];
518
+ bits -= 5;
519
+ }
520
+ }
521
+ if (bits > 0)
522
+ out += BASE32_ALPHABET[(value << (5 - bits)) & 31];
523
+ while (out.length % 8 !== 0)
524
+ out += '=';
525
+ return out;
526
+ }
527
+ /** RFC 4648 base32 decode; padding optional, case insensitive, throws on invalid characters. */
528
+ function base32Decode(str) {
529
+ const clean = str.replace(/=+$/, '').toUpperCase();
530
+ const out = new Uint8Array(Math.floor(clean.length * 5 / 8));
531
+ let bits = 0;
532
+ let value = 0;
533
+ let index = 0;
534
+ for (let i = 0; i < clean.length; i++) {
535
+ const v = BASE32_LOOKUP[clean[i]];
536
+ if (v === undefined)
537
+ throw new Error(`[chains.algorand] invalid base32 character "${clean[i]}"`);
538
+ value = ((value << 5) | v) & 0xffff;
539
+ bits += 5;
540
+ if (bits >= 8) {
541
+ out[index++] = (value >>> (bits - 8)) & 0xff;
542
+ bits -= 8;
543
+ }
544
+ }
545
+ return out;
546
+ }
547
+ /** SHA-512/256 digest (Algorand's "genericHash"). */
548
+ function sha512_256(bytes) {
549
+ return new Uint8Array((0, crypto_1.createHash)('sha512-256').update(bytes).digest());
550
+ }
551
+ function concatArrays(...arrs) {
552
+ const size = arrs.reduce((sum, arr) => sum + arr.length, 0);
553
+ const c = new Uint8Array(size);
554
+ let offset = 0;
555
+ for (const arr of arrs) {
556
+ c.set(arr, offset);
557
+ offset += arr.length;
558
+ }
559
+ return c;
560
+ }
561
+ /** 32 byte public key -> 58 char address (base32 of key + 4 byte sha512/256 checksum, padding stripped). */
562
+ function encodeAddress(publicKey) {
563
+ if (publicKey.length !== PUBLIC_KEY_LENGTH)
564
+ throw new Error(`[chains.algorand] public key must be ${PUBLIC_KEY_LENGTH} bytes, got ${publicKey.length}`);
565
+ const checksum = sha512_256(publicKey).slice(PUBLIC_KEY_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, PUBLIC_KEY_LENGTH);
566
+ return base32Encode(concatArrays(publicKey, checksum)).slice(0, ALGORAND_ADDRESS_LENGTH);
567
+ }
568
+ /**
569
+ * 58 char address -> `{ publicKey, checksum }`; throws when the length, the checksum or the
570
+ * encoding is wrong. Like algosdk, only the canonical upper-case base32 form is accepted: the
571
+ * last character carries 2 padding bits that must be zero, so re-encoding must reproduce the input.
572
+ */
573
+ function decodeAddress(address) {
574
+ if (typeof address !== 'string' || address.length !== ALGORAND_ADDRESS_LENGTH)
575
+ throw new Error(`[chains.algorand] address must be ${ALGORAND_ADDRESS_LENGTH} characters: ${address}`);
576
+ const decoded = base32Decode(address);
577
+ const publicKey = decoded.slice(0, PUBLIC_KEY_LENGTH);
578
+ const checksum = decoded.slice(PUBLIC_KEY_LENGTH, PUBLIC_KEY_LENGTH + ALGORAND_CHECKSUM_BYTE_LENGTH);
579
+ const expected = sha512_256(publicKey).slice(PUBLIC_KEY_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, PUBLIC_KEY_LENGTH);
580
+ for (let i = 0; i < ALGORAND_CHECKSUM_BYTE_LENGTH; i++) {
581
+ if (checksum[i] !== expected[i])
582
+ throw new Error(`[chains.algorand] address checksum mismatch: ${address}`);
583
+ }
584
+ if (encodeAddress(publicKey) !== address)
585
+ throw new Error(`[chains.algorand] address is not canonically encoded: ${address}`);
586
+ return { publicKey, checksum };
587
+ }
588
+ function isValidAddress(address) {
589
+ try {
590
+ decodeAddress(address);
591
+ return true;
592
+ }
593
+ catch {
594
+ return false;
595
+ }
596
+ }
597
+ /** Big-endian 8 byte encoding of an unsigned 64-bit integer. */
598
+ function encodeUint64(num) {
599
+ let value;
600
+ try {
601
+ if (typeof num === 'number' && !Number.isInteger(num))
602
+ throw new Error('not an integer');
603
+ value = BigInt(num);
604
+ }
605
+ catch {
606
+ throw new Error(`[chains.algorand] Input is not a 64-bit unsigned integer: ${num}`);
607
+ }
608
+ if (value < BigInt(0) || value > MAX_UINT64)
609
+ throw new Error(`[chains.algorand] Input is not a 64-bit unsigned integer: ${num}`);
610
+ const buf = Buffer.alloc(8);
611
+ buf.writeBigUInt64BE(value);
612
+ return new Uint8Array(buf);
613
+ }
614
+ /** Escrow address of an application: encodeAddress(sha512_256("appID" || uint64(appId))). */
615
+ function getApplicationAddress(appId) {
616
+ const toBeSigned = concatArrays(APP_ID_PREFIX, encodeUint64(appId));
617
+ return encodeAddress(sha512_256(toBeSigned));
618
+ }