@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.
- package/build/ChainApi.d.ts +23 -1
- package/build/chains/algorand.d.ts +277 -0
- package/build/chains/algorand.js +618 -0
- package/build/chains/aptos.d.ts +230 -0
- package/build/chains/aptos.js +612 -0
- package/build/chains/cardano.d.ts +242 -0
- package/build/chains/cardano.js +407 -0
- package/build/chains/cosmos.d.ts +252 -0
- package/build/chains/cosmos.js +618 -0
- package/build/chains/icp.d.ts +235 -0
- package/build/chains/icp.js +1040 -0
- package/build/chains/index.d.ts +23 -0
- package/build/chains/index.js +59 -0
- package/build/chains/near.d.ts +192 -0
- package/build/chains/near.js +446 -0
- package/build/chains/rpc.d.ts +125 -0
- package/build/chains/rpc.js +326 -0
- package/build/chains/starknet.d.ts +205 -0
- package/build/chains/starknet.js +884 -0
- package/build/chains/stellar.d.ts +410 -0
- package/build/chains/stellar.js +1082 -0
- package/build/chains/substrate.d.ts +333 -0
- package/build/chains/substrate.js +1042 -0
- package/build/chains/sui.d.ts +365 -0
- package/build/chains/sui.js +1135 -0
- package/build/chains/svm.d.ts +340 -0
- package/build/chains/svm.js +760 -0
- package/build/chains/tezos.d.ts +225 -0
- package/build/chains/tezos.js +412 -0
- package/build/chains/ton.d.ts +353 -0
- package/build/chains/ton.js +788 -0
- package/build/chains/tron.d.ts +45 -0
- package/build/chains/tron.js +110 -0
- package/build/chains/utxo.d.ts +202 -0
- package/build/chains/utxo.js +592 -0
- package/build/chains/xrpl.d.ts +179 -0
- package/build/chains/xrpl.js +467 -0
- package/build/index.d.ts +2 -1
- package/build/index.js +3 -1
- package/build/providers.json +1 -1
- package/build/util/chainUtils/data.json +11 -0
- package/build/util/chainUtils/index.d.ts +1 -0
- package/build/util/chainUtils/index.js +23 -0
- package/build/util/common.js +2 -3
- package/build/util/cosmos.d.ts +1 -11
- package/build/util/cosmos.js +6 -66
- package/build/util/env.js +1 -0
- package/build/util/indexer.compatibility.d.ts +6 -0
- package/build/util/indexer.compatibility.js +64 -0
- package/build/util/indexer.d.ts +24 -2
- package/build/util/indexer.js +215 -282
- package/build/util/indexerSql.d.ts +8 -1
- package/build/util/indexerSql.js +53 -22
- package/build/util/logs.decode.shared.js +5 -4
- package/build/util/transactions.d.ts +23 -1
- package/build/util/updateChainLabels.d.ts +1 -0
- package/build/util/updateChainLabels.js +5 -0
- package/build/util/updateProviderList.js +38 -17
- package/package.json +20 -7
- package/README.md +0 -41
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
export declare const DEFAULT_ENDPOINT = "https://cardano-mainnet.blockfrost.io/api/v0";
|
|
2
|
+
/** Blockfrost hard limit for `count` */
|
|
3
|
+
export declare const MAX_PAGE_SIZE = 100;
|
|
4
|
+
/**
|
|
5
|
+
* Shelley era (mainnet) started at slot 4492800 / unix 1596059091 with 1s slots, so
|
|
6
|
+
* `slot = timestamp - SHELLEY_SLOT_OFFSET`. Byron blocks (before that) use 20s slots and
|
|
7
|
+
* are not covered by the formula.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SHELLEY_START_TIME = 1596059091;
|
|
10
|
+
export declare const SHELLEY_START_SLOT = 4492800;
|
|
11
|
+
export declare const SHELLEY_SLOT_OFFSET: number;
|
|
12
|
+
/** Base url list: `CARDANO_BLOCKFROST` env (comma separated) wins, then `DEFAULT_ENDPOINT`. */
|
|
13
|
+
export declare function getEndpointList(): string[];
|
|
14
|
+
/** First configured base url. */
|
|
15
|
+
export declare function getEndpoint(): string;
|
|
16
|
+
/** Blockfrost project id from `BLOCKFROST_PROJECT_ID`; throws when unset. */
|
|
17
|
+
export declare function getProjectId(): string;
|
|
18
|
+
export interface AssetAmount {
|
|
19
|
+
/** `lovelace` or `<policyId><assetNameHex>` */
|
|
20
|
+
unit: string;
|
|
21
|
+
quantity: string;
|
|
22
|
+
}
|
|
23
|
+
export interface AddressInfo {
|
|
24
|
+
address: string;
|
|
25
|
+
amount: AssetAmount[];
|
|
26
|
+
stake_address: string | null;
|
|
27
|
+
type: string;
|
|
28
|
+
script: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface Utxo {
|
|
31
|
+
address: string;
|
|
32
|
+
tx_hash: string;
|
|
33
|
+
output_index: number;
|
|
34
|
+
amount: AssetAmount[];
|
|
35
|
+
block: string;
|
|
36
|
+
data_hash: string | null;
|
|
37
|
+
inline_datum: string | null;
|
|
38
|
+
reference_script_hash: string | null;
|
|
39
|
+
}
|
|
40
|
+
export interface AddressTransaction {
|
|
41
|
+
tx_hash: string;
|
|
42
|
+
tx_index: number;
|
|
43
|
+
block_height: number;
|
|
44
|
+
block_time: number;
|
|
45
|
+
}
|
|
46
|
+
export interface TxUtxos {
|
|
47
|
+
hash: string;
|
|
48
|
+
inputs: (Utxo & {
|
|
49
|
+
collateral: boolean;
|
|
50
|
+
reference: boolean;
|
|
51
|
+
})[];
|
|
52
|
+
outputs: (Utxo & {
|
|
53
|
+
collateral: boolean;
|
|
54
|
+
consumed_by_tx?: string | null;
|
|
55
|
+
})[];
|
|
56
|
+
}
|
|
57
|
+
export interface Asset {
|
|
58
|
+
asset: string;
|
|
59
|
+
policy_id: string;
|
|
60
|
+
asset_name: string | null;
|
|
61
|
+
fingerprint: string;
|
|
62
|
+
quantity: string;
|
|
63
|
+
initial_mint_tx_hash: string;
|
|
64
|
+
mint_or_burn_count: number;
|
|
65
|
+
onchain_metadata: Record<string, any> | null;
|
|
66
|
+
onchain_metadata_standard?: string | null;
|
|
67
|
+
metadata: {
|
|
68
|
+
name?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
ticker?: string | null;
|
|
71
|
+
url?: string | null;
|
|
72
|
+
logo?: string | null;
|
|
73
|
+
decimals?: number | null;
|
|
74
|
+
} | null;
|
|
75
|
+
}
|
|
76
|
+
export interface Block {
|
|
77
|
+
/** block height */
|
|
78
|
+
number: number;
|
|
79
|
+
/** unix seconds */
|
|
80
|
+
timestamp: number;
|
|
81
|
+
hash: string;
|
|
82
|
+
slot: number;
|
|
83
|
+
epoch: number;
|
|
84
|
+
/** raw Blockfrost block record */
|
|
85
|
+
raw: any;
|
|
86
|
+
}
|
|
87
|
+
export interface BlockfrostOptions {
|
|
88
|
+
/** path relative to the api root, e.g. `/addresses/addr1...` */
|
|
89
|
+
path: string;
|
|
90
|
+
params?: Record<string, any>;
|
|
91
|
+
/** return `null` on 404 instead of throwing */
|
|
92
|
+
allowNotFound?: boolean;
|
|
93
|
+
timeout?: number;
|
|
94
|
+
retries?: number;
|
|
95
|
+
}
|
|
96
|
+
export interface PagedOptions {
|
|
97
|
+
path: string;
|
|
98
|
+
params?: Record<string, any>;
|
|
99
|
+
/** page size, max 100 (Blockfrost limit) */
|
|
100
|
+
count?: number;
|
|
101
|
+
/** stop after this many pages (safety cap) */
|
|
102
|
+
maxPages?: number;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* GET `path` from Blockfrost. Rotates through configured base urls, retries transient
|
|
106
|
+
* failures (5xx / 429) and, when `allowNotFound`, turns a 404 into `null` before the retry
|
|
107
|
+
* layer sees it.
|
|
108
|
+
*/
|
|
109
|
+
export declare function blockfrost({ path, params, allowNotFound, timeout, retries }: BlockfrostOptions): Promise<any>;
|
|
110
|
+
/**
|
|
111
|
+
* Walk every page of a list endpoint (`count` per page, `page` 1..n) and concatenate the
|
|
112
|
+
* results. Stops on the first short / empty page or after `maxPages`.
|
|
113
|
+
*/
|
|
114
|
+
export declare function blockfrostAll<T = any>({ path, params, count, maxPages }: PagedOptions): Promise<T[]>;
|
|
115
|
+
/** `/addresses/{address}`: balances (`amount`), stake address, type. `null` for unused addresses (404). */
|
|
116
|
+
export declare function getAddress({ address }: {
|
|
117
|
+
address: string;
|
|
118
|
+
}): Promise<AddressInfo | null>;
|
|
119
|
+
/** All assets held by `address` as `{ unit, quantity }[]` (`lovelace` included). Empty for unused addresses. */
|
|
120
|
+
export declare function getAddressAssets({ address }: {
|
|
121
|
+
address: string;
|
|
122
|
+
}): Promise<AssetAmount[]>;
|
|
123
|
+
/** ADA balance of `address` in lovelace (string). `'0'` for unused addresses. */
|
|
124
|
+
export declare function getAdaBalance({ address }: {
|
|
125
|
+
address: string;
|
|
126
|
+
}): Promise<string>;
|
|
127
|
+
/** Balance of `asset` (`<policyId><assetNameHex>` or `lovelace`) held by `address`, as a string. */
|
|
128
|
+
export declare function getTokenBalance({ address, asset }: {
|
|
129
|
+
address: string;
|
|
130
|
+
asset: string;
|
|
131
|
+
}): Promise<string>;
|
|
132
|
+
/** Every utxo at `address` (all pages). */
|
|
133
|
+
export declare function getAddressUtxos({ address, maxPages }: {
|
|
134
|
+
address: string;
|
|
135
|
+
maxPages?: number;
|
|
136
|
+
}): Promise<Utxo[]>;
|
|
137
|
+
/** Every utxo at `address` containing `asset` (all pages). */
|
|
138
|
+
export declare function getAddressUtxosByAsset({ address, asset, maxPages }: {
|
|
139
|
+
address: string;
|
|
140
|
+
asset: string;
|
|
141
|
+
maxPages?: number;
|
|
142
|
+
}): Promise<Utxo[]>;
|
|
143
|
+
/**
|
|
144
|
+
* Transactions involving `address` (all pages). `from` / `to` are block heights or
|
|
145
|
+
* `height:txIndex` strings, inclusive, as accepted by Blockfrost. `order` defaults to `asc`.
|
|
146
|
+
*/
|
|
147
|
+
export declare function getAddressTransactions({ address, from, to, order, maxPages }: {
|
|
148
|
+
address: string;
|
|
149
|
+
from?: number | string;
|
|
150
|
+
to?: number | string;
|
|
151
|
+
order?: 'asc' | 'desc';
|
|
152
|
+
maxPages?: number;
|
|
153
|
+
}): Promise<AddressTransaction[]>;
|
|
154
|
+
/** `/txs/{hash}/utxos`: inputs and outputs of a transaction. */
|
|
155
|
+
export declare function getTxUtxos({ txHash }: {
|
|
156
|
+
txHash: string;
|
|
157
|
+
}): Promise<TxUtxos>;
|
|
158
|
+
/** `/txs/{hash}/redeemers` */
|
|
159
|
+
export declare function getTxRedeemers({ txHash }: {
|
|
160
|
+
txHash: string;
|
|
161
|
+
}): Promise<any[]>;
|
|
162
|
+
/** `/txs/{hash}/metadata` */
|
|
163
|
+
export declare function getTxMetadata({ txHash }: {
|
|
164
|
+
txHash: string;
|
|
165
|
+
}): Promise<any[]>;
|
|
166
|
+
/** `/scripts/datum/{hash}`: `{ json_value }`, or `null` when unknown. */
|
|
167
|
+
export declare function getScriptDatum({ datumHash }: {
|
|
168
|
+
datumHash: string;
|
|
169
|
+
}): Promise<{
|
|
170
|
+
json_value: any;
|
|
171
|
+
} | null>;
|
|
172
|
+
/** `/assets/{assetId}` record, or `null` when the asset was never minted. */
|
|
173
|
+
export declare function getAsset({ assetId }: {
|
|
174
|
+
assetId: string;
|
|
175
|
+
}): Promise<Asset | null>;
|
|
176
|
+
/**
|
|
177
|
+
* Circulating supply (`quantity`, string) and decimals of an asset. Decimals come from the
|
|
178
|
+
* off-chain token registry (`metadata.decimals`) or CIP-68 on-chain metadata
|
|
179
|
+
* (`onchain_metadata.decimals`); `undefined` when neither is set.
|
|
180
|
+
*/
|
|
181
|
+
export declare function getAssetSupply({ assetId }: {
|
|
182
|
+
assetId: string;
|
|
183
|
+
}): Promise<{
|
|
184
|
+
supply: string;
|
|
185
|
+
decimals: number | undefined;
|
|
186
|
+
}>;
|
|
187
|
+
export declare function getAssetDecimals(asset: Asset | null | undefined): number | undefined;
|
|
188
|
+
/** `/assets/{assetId}/addresses`: every `{ address, quantity }` holding the asset (all pages). */
|
|
189
|
+
export declare function getAssetAddresses({ assetId, maxPages }: {
|
|
190
|
+
assetId: string;
|
|
191
|
+
maxPages?: number;
|
|
192
|
+
}): Promise<{
|
|
193
|
+
address: string;
|
|
194
|
+
quantity: string;
|
|
195
|
+
}[]>;
|
|
196
|
+
/** `/accounts/{stakeAddress}/addresses`: every payment address controlled by a stake key (all pages). */
|
|
197
|
+
export declare function getAccountAddresses({ stakeAddress, maxPages }: {
|
|
198
|
+
stakeAddress: string;
|
|
199
|
+
maxPages?: number;
|
|
200
|
+
}): Promise<string[]>;
|
|
201
|
+
/** `/blocks/latest` as `{ number, timestamp, hash, slot, epoch, raw }`. */
|
|
202
|
+
export declare function getLatestBlock(): Promise<Block>;
|
|
203
|
+
/** `/blocks/{hashOrNumber}`; `null` when unknown. */
|
|
204
|
+
export declare function getBlock({ hashOrNumber }: {
|
|
205
|
+
hashOrNumber: string | number;
|
|
206
|
+
}): Promise<Block | null>;
|
|
207
|
+
/** `/blocks/slot/{slot}`; `null` when no block was minted in that slot. */
|
|
208
|
+
export declare function getBlockBySlot({ slot }: {
|
|
209
|
+
slot: number;
|
|
210
|
+
}): Promise<Block | null>;
|
|
211
|
+
/**
|
|
212
|
+
* Last block with `time <= timestamp` (unix seconds). For Shelley-era timestamps the slot is
|
|
213
|
+
* derived directly (`slot = timestamp - SHELLEY_SLOT_OFFSET`) and `/blocks/slot/{slot}` is
|
|
214
|
+
* tried first; otherwise (or when that slot is empty) an interpolation search over block
|
|
215
|
+
* heights, seeded from the latest block, converges in a handful of requests.
|
|
216
|
+
*/
|
|
217
|
+
export declare function getBlockAtTimestamp({ timestamp }: {
|
|
218
|
+
timestamp: number;
|
|
219
|
+
}): Promise<Block>;
|
|
220
|
+
/** `/epochs/{epoch}` (or `/epochs/latest`). */
|
|
221
|
+
export declare function getEpoch({ epoch }?: {
|
|
222
|
+
epoch?: number | 'latest';
|
|
223
|
+
}): Promise<any>;
|
|
224
|
+
export interface ParsedAssetId {
|
|
225
|
+
policyId: string;
|
|
226
|
+
assetNameHex: string;
|
|
227
|
+
/** utf8 decoded asset name when it is printable ascii/utf8, otherwise the hex */
|
|
228
|
+
assetName: string;
|
|
229
|
+
}
|
|
230
|
+
/** Split `<policyId><assetNameHex>` (or `lovelace`) into its parts. */
|
|
231
|
+
export declare function parseAssetId(assetId: string): ParsedAssetId;
|
|
232
|
+
/** Hex asset name -> utf8 when it round-trips and is printable, else the hex itself. */
|
|
233
|
+
export declare function decodeAssetName(assetNameHex: string): string;
|
|
234
|
+
/**
|
|
235
|
+
* `policyId + assetNameHex`. `assetName` is taken as hex when it is a valid even-length hex
|
|
236
|
+
* string, otherwise utf8 encoded; pass `encoding` to force one.
|
|
237
|
+
*/
|
|
238
|
+
export declare function buildAssetId(policyId: string, assetName?: string, encoding?: 'hex' | 'utf8'): string;
|
|
239
|
+
/** `stake1...` (mainnet) / `stake_test1...` bech32 reward addresses. */
|
|
240
|
+
export declare function isStakeAddress(address: string): boolean;
|
|
241
|
+
/** lovelace (string | number | bigint) -> ADA as a number. */
|
|
242
|
+
export declare function lovelaceToAda(lovelace: string | number | bigint): number;
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SHELLEY_SLOT_OFFSET = exports.SHELLEY_START_SLOT = exports.SHELLEY_START_TIME = exports.MAX_PAGE_SIZE = exports.DEFAULT_ENDPOINT = void 0;
|
|
7
|
+
exports.getEndpointList = getEndpointList;
|
|
8
|
+
exports.getEndpoint = getEndpoint;
|
|
9
|
+
exports.getProjectId = getProjectId;
|
|
10
|
+
exports.blockfrost = blockfrost;
|
|
11
|
+
exports.blockfrostAll = blockfrostAll;
|
|
12
|
+
exports.getAddress = getAddress;
|
|
13
|
+
exports.getAddressAssets = getAddressAssets;
|
|
14
|
+
exports.getAdaBalance = getAdaBalance;
|
|
15
|
+
exports.getTokenBalance = getTokenBalance;
|
|
16
|
+
exports.getAddressUtxos = getAddressUtxos;
|
|
17
|
+
exports.getAddressUtxosByAsset = getAddressUtxosByAsset;
|
|
18
|
+
exports.getAddressTransactions = getAddressTransactions;
|
|
19
|
+
exports.getTxUtxos = getTxUtxos;
|
|
20
|
+
exports.getTxRedeemers = getTxRedeemers;
|
|
21
|
+
exports.getTxMetadata = getTxMetadata;
|
|
22
|
+
exports.getScriptDatum = getScriptDatum;
|
|
23
|
+
exports.getAsset = getAsset;
|
|
24
|
+
exports.getAssetSupply = getAssetSupply;
|
|
25
|
+
exports.getAssetDecimals = getAssetDecimals;
|
|
26
|
+
exports.getAssetAddresses = getAssetAddresses;
|
|
27
|
+
exports.getAccountAddresses = getAccountAddresses;
|
|
28
|
+
exports.getLatestBlock = getLatestBlock;
|
|
29
|
+
exports.getBlock = getBlock;
|
|
30
|
+
exports.getBlockBySlot = getBlockBySlot;
|
|
31
|
+
exports.getBlockAtTimestamp = getBlockAtTimestamp;
|
|
32
|
+
exports.getEpoch = getEpoch;
|
|
33
|
+
exports.parseAssetId = parseAssetId;
|
|
34
|
+
exports.decodeAssetName = decodeAssetName;
|
|
35
|
+
exports.buildAssetId = buildAssetId;
|
|
36
|
+
exports.isStakeAddress = isStakeAddress;
|
|
37
|
+
exports.lovelaceToAda = lovelaceToAda;
|
|
38
|
+
/**
|
|
39
|
+
* Cardano client over the Blockfrost REST API (mainnet by default).
|
|
40
|
+
*
|
|
41
|
+
* Replaces the chain access parts of:
|
|
42
|
+
* - DefiLlama-Adapters projects/helper/chain/cardano/blockfrost.js (getAssets, getAddressesUTXOs,
|
|
43
|
+
* getTxUtxos, getTxsRedeemers, getTxsMetadata, assetsAddresses, addressesUtxosAssetAll,
|
|
44
|
+
* getTokensMinted, getScriptsDatum, getAccountAddresses)
|
|
45
|
+
* - DefiLlama-Adapters projects/helper/chain/cardano.js (getAda, getAdaInAddress, getTokenBalance)
|
|
46
|
+
* - peggedassets-server src/adapters/peggedAssets/helper/cardano.ts (getAsset, getTotalSupply,
|
|
47
|
+
* getTokenBalance, addressesUtxosAssetAll, getScriptsDatum)
|
|
48
|
+
* - dimension-adapters helpers/cardano.ts (blockfrost(path), getAdaReceived -> address transactions
|
|
49
|
+
* in a time window + tx utxos)
|
|
50
|
+
* - defillama-server coins/src/adapters/markets/minswap.ts (Blockfrost axios client shape)
|
|
51
|
+
*
|
|
52
|
+
* TVL coupling (sumTokens / Balances / Minswap GraphQL pricing) is intentionally left out;
|
|
53
|
+
* every function returns raw chain values (lovelace / asset quantities as strings).
|
|
54
|
+
*
|
|
55
|
+
* Configuration:
|
|
56
|
+
* - `BLOCKFROST_PROJECT_ID` (required, also `SDK_` / `LLAMA_SDK_` prefixed): Blockfrost api key.
|
|
57
|
+
* No key is embedded in the sdk.
|
|
58
|
+
* - `CARDANO_BLOCKFROST` (optional, comma separated): api base url(s), default
|
|
59
|
+
* `https://cardano-mainnet.blockfrost.io/api/v0`.
|
|
60
|
+
* - `CARDANO_RPC_CONCURRENCY` (optional): max parallel requests, default 5.
|
|
61
|
+
*
|
|
62
|
+
* Usage: `sdk.chains.cardano.getAdaBalance({ address })`
|
|
63
|
+
*/
|
|
64
|
+
const axios_1 = __importDefault(require("axios"));
|
|
65
|
+
const env_1 = require("../util/env");
|
|
66
|
+
const debugLog_1 = require("../util/debugLog");
|
|
67
|
+
const rpc_1 = require("./rpc");
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// config
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
exports.DEFAULT_ENDPOINT = "https://cardano-mainnet.blockfrost.io/api/v0";
|
|
72
|
+
const ENDPOINT_ENV_KEY = 'CARDANO_BLOCKFROST';
|
|
73
|
+
const PROJECT_ID_ENV_KEY = 'BLOCKFROST_PROJECT_ID';
|
|
74
|
+
const LIMITER_KEY = 'cardano';
|
|
75
|
+
const CONCURRENCY = 5;
|
|
76
|
+
const DEFAULT_TIMEOUT = 120000;
|
|
77
|
+
/** Blockfrost hard limit for `count` */
|
|
78
|
+
exports.MAX_PAGE_SIZE = 100;
|
|
79
|
+
/**
|
|
80
|
+
* Shelley era (mainnet) started at slot 4492800 / unix 1596059091 with 1s slots, so
|
|
81
|
+
* `slot = timestamp - SHELLEY_SLOT_OFFSET`. Byron blocks (before that) use 20s slots and
|
|
82
|
+
* are not covered by the formula.
|
|
83
|
+
*/
|
|
84
|
+
exports.SHELLEY_START_TIME = 1596059091;
|
|
85
|
+
exports.SHELLEY_START_SLOT = 4492800;
|
|
86
|
+
exports.SHELLEY_SLOT_OFFSET = exports.SHELLEY_START_TIME - exports.SHELLEY_START_SLOT; // 1591566291
|
|
87
|
+
/** Base url list: `CARDANO_BLOCKFROST` env (comma separated) wins, then `DEFAULT_ENDPOINT`. */
|
|
88
|
+
function getEndpointList() {
|
|
89
|
+
return (0, rpc_1.getEndpoints)('cardano', exports.DEFAULT_ENDPOINT, { envKey: ENDPOINT_ENV_KEY });
|
|
90
|
+
}
|
|
91
|
+
/** First configured base url. */
|
|
92
|
+
function getEndpoint() {
|
|
93
|
+
return getEndpointList()[0];
|
|
94
|
+
}
|
|
95
|
+
/** Blockfrost project id from `BLOCKFROST_PROJECT_ID`; throws when unset. */
|
|
96
|
+
function getProjectId() {
|
|
97
|
+
const value = (0, env_1.getEnvValue)(PROJECT_ID_ENV_KEY);
|
|
98
|
+
if (!value)
|
|
99
|
+
throw new Error(`[chains.cardano] missing Blockfrost api key: set the ${PROJECT_ID_ENV_KEY} env var (https://blockfrost.io)`);
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// client
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
const NOT_FOUND = Symbol('not-found');
|
|
106
|
+
function isNotFound(e) {
|
|
107
|
+
var _a;
|
|
108
|
+
return ((_a = e === null || e === void 0 ? void 0 : e.response) === null || _a === void 0 ? void 0 : _a.status) === 404;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* GET `path` from Blockfrost. Rotates through configured base urls, retries transient
|
|
112
|
+
* failures (5xx / 429) and, when `allowNotFound`, turns a 404 into `null` before the retry
|
|
113
|
+
* layer sees it.
|
|
114
|
+
*/
|
|
115
|
+
async function blockfrost({ path, params, allowNotFound = false, timeout = DEFAULT_TIMEOUT, retries }) {
|
|
116
|
+
const endpoints = getEndpointList();
|
|
117
|
+
const headers = { project_id: getProjectId(), 'Content-Type': 'application/json' };
|
|
118
|
+
const config = { timeout, headers, params };
|
|
119
|
+
const limiter = (0, rpc_1.getLimiter)(LIMITER_KEY, CONCURRENCY);
|
|
120
|
+
const res = await (0, rpc_1.withRetry)(async (attempt) => {
|
|
121
|
+
const base = endpoints[attempt % endpoints.length];
|
|
122
|
+
const url = (0, rpc_1.joinUrl)(base, path);
|
|
123
|
+
return limiter(async () => {
|
|
124
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
125
|
+
try {
|
|
126
|
+
const response = await axios_1.default.get(url, config);
|
|
127
|
+
return response.data;
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
if (allowNotFound && isNotFound(e)) {
|
|
131
|
+
(0, debugLog_1.debugLog)(`[chains.cardano] GET ${path} -> 404 (treated as not found)`);
|
|
132
|
+
return NOT_FOUND;
|
|
133
|
+
}
|
|
134
|
+
const status = (_a = e === null || e === void 0 ? void 0 : e.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
135
|
+
const detail = (_g = (_d = (_c = (_b = e === null || e === void 0 ? void 0 : e.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.message) !== null && _d !== void 0 ? _d : (_f = (_e = e === null || e === void 0 ? void 0 : e.response) === null || _e === void 0 ? void 0 : _e.data) === null || _f === void 0 ? void 0 : _f.error) !== null && _g !== void 0 ? _g : e === null || e === void 0 ? void 0 : e.message;
|
|
136
|
+
const err = new Error(`[cardano] GET ${path} failed${status ? ` [${status}]` : ''}: ${String(detail !== null && detail !== void 0 ? detail : '').slice(0, 300)} (${(0, rpc_1.shortUrl)(url)})`);
|
|
137
|
+
err.response = e === null || e === void 0 ? void 0 : e.response;
|
|
138
|
+
err.status = status;
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}, {
|
|
143
|
+
label: `cardano GET ${path.split('?')[0].slice(0, 80)}`,
|
|
144
|
+
retries: retries !== null && retries !== void 0 ? retries : Math.max(3, endpoints.length),
|
|
145
|
+
shouldRetry: rpc_1.isRetryableError,
|
|
146
|
+
});
|
|
147
|
+
return res === NOT_FOUND ? null : res;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Walk every page of a list endpoint (`count` per page, `page` 1..n) and concatenate the
|
|
151
|
+
* results. Stops on the first short / empty page or after `maxPages`.
|
|
152
|
+
*/
|
|
153
|
+
async function blockfrostAll({ path, params, count = exports.MAX_PAGE_SIZE, maxPages }) {
|
|
154
|
+
if (count < 1 || count > exports.MAX_PAGE_SIZE)
|
|
155
|
+
throw new Error(`[chains.cardano] blockfrostAll: count must be between 1 and ${exports.MAX_PAGE_SIZE}`);
|
|
156
|
+
const results = [];
|
|
157
|
+
let page = 1;
|
|
158
|
+
while (true) {
|
|
159
|
+
const res = await blockfrost({ path, params: { ...(params !== null && params !== void 0 ? params : {}), count, page } });
|
|
160
|
+
if (!Array.isArray(res))
|
|
161
|
+
throw new Error(`[chains.cardano] GET ${path}: expected an array on page ${page}`);
|
|
162
|
+
results.push(...res);
|
|
163
|
+
if (res.length < count)
|
|
164
|
+
break;
|
|
165
|
+
if (maxPages !== undefined && page >= maxPages) {
|
|
166
|
+
(0, debugLog_1.debugLog)(`[chains.cardano] GET ${path}: stopped after maxPages=${maxPages}, result may be truncated`);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
page++;
|
|
170
|
+
}
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// addresses
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
/** `/addresses/{address}`: balances (`amount`), stake address, type. `null` for unused addresses (404). */
|
|
177
|
+
async function getAddress({ address }) {
|
|
178
|
+
return blockfrost({ path: `/addresses/${address}`, allowNotFound: true });
|
|
179
|
+
}
|
|
180
|
+
/** All assets held by `address` as `{ unit, quantity }[]` (`lovelace` included). Empty for unused addresses. */
|
|
181
|
+
async function getAddressAssets({ address }) {
|
|
182
|
+
var _a;
|
|
183
|
+
const info = await getAddress({ address });
|
|
184
|
+
return (_a = info === null || info === void 0 ? void 0 : info.amount) !== null && _a !== void 0 ? _a : [];
|
|
185
|
+
}
|
|
186
|
+
/** ADA balance of `address` in lovelace (string). `'0'` for unused addresses. */
|
|
187
|
+
async function getAdaBalance({ address }) {
|
|
188
|
+
return getTokenBalance({ address, asset: 'lovelace' });
|
|
189
|
+
}
|
|
190
|
+
/** Balance of `asset` (`<policyId><assetNameHex>` or `lovelace`) held by `address`, as a string. */
|
|
191
|
+
async function getTokenBalance({ address, asset }) {
|
|
192
|
+
var _a, _b;
|
|
193
|
+
const assets = await getAddressAssets({ address });
|
|
194
|
+
return (_b = (_a = assets.find(i => i.unit === asset)) === null || _a === void 0 ? void 0 : _a.quantity) !== null && _b !== void 0 ? _b : '0';
|
|
195
|
+
}
|
|
196
|
+
/** Every utxo at `address` (all pages). */
|
|
197
|
+
async function getAddressUtxos({ address, maxPages }) {
|
|
198
|
+
return blockfrostAll({ path: `/addresses/${address}/utxos`, maxPages });
|
|
199
|
+
}
|
|
200
|
+
/** Every utxo at `address` containing `asset` (all pages). */
|
|
201
|
+
async function getAddressUtxosByAsset({ address, asset, maxPages }) {
|
|
202
|
+
return blockfrostAll({ path: `/addresses/${address}/utxos/${asset}`, maxPages });
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Transactions involving `address` (all pages). `from` / `to` are block heights or
|
|
206
|
+
* `height:txIndex` strings, inclusive, as accepted by Blockfrost. `order` defaults to `asc`.
|
|
207
|
+
*/
|
|
208
|
+
async function getAddressTransactions({ address, from, to, order, maxPages }) {
|
|
209
|
+
const params = {};
|
|
210
|
+
if (from !== undefined)
|
|
211
|
+
params.from = String(from);
|
|
212
|
+
if (to !== undefined)
|
|
213
|
+
params.to = String(to);
|
|
214
|
+
if (order)
|
|
215
|
+
params.order = order;
|
|
216
|
+
return blockfrostAll({ path: `/addresses/${address}/transactions`, params, maxPages });
|
|
217
|
+
}
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// transactions / scripts
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
/** `/txs/{hash}/utxos`: inputs and outputs of a transaction. */
|
|
222
|
+
async function getTxUtxos({ txHash }) {
|
|
223
|
+
return blockfrost({ path: `/txs/${txHash}/utxos` });
|
|
224
|
+
}
|
|
225
|
+
/** `/txs/{hash}/redeemers` */
|
|
226
|
+
async function getTxRedeemers({ txHash }) {
|
|
227
|
+
return blockfrost({ path: `/txs/${txHash}/redeemers` });
|
|
228
|
+
}
|
|
229
|
+
/** `/txs/{hash}/metadata` */
|
|
230
|
+
async function getTxMetadata({ txHash }) {
|
|
231
|
+
return blockfrost({ path: `/txs/${txHash}/metadata` });
|
|
232
|
+
}
|
|
233
|
+
/** `/scripts/datum/{hash}`: `{ json_value }`, or `null` when unknown. */
|
|
234
|
+
async function getScriptDatum({ datumHash }) {
|
|
235
|
+
return blockfrost({ path: `/scripts/datum/${datumHash}`, allowNotFound: true });
|
|
236
|
+
}
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// assets
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
/** `/assets/{assetId}` record, or `null` when the asset was never minted. */
|
|
241
|
+
async function getAsset({ assetId }) {
|
|
242
|
+
return blockfrost({ path: `/assets/${assetId}`, allowNotFound: true });
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Circulating supply (`quantity`, string) and decimals of an asset. Decimals come from the
|
|
246
|
+
* off-chain token registry (`metadata.decimals`) or CIP-68 on-chain metadata
|
|
247
|
+
* (`onchain_metadata.decimals`); `undefined` when neither is set.
|
|
248
|
+
*/
|
|
249
|
+
async function getAssetSupply({ assetId }) {
|
|
250
|
+
var _a;
|
|
251
|
+
const asset = await getAsset({ assetId });
|
|
252
|
+
if (!asset)
|
|
253
|
+
throw new Error(`[chains.cardano] asset ${assetId} not found`);
|
|
254
|
+
return { supply: String((_a = asset.quantity) !== null && _a !== void 0 ? _a : '0'), decimals: getAssetDecimals(asset) };
|
|
255
|
+
}
|
|
256
|
+
function getAssetDecimals(asset) {
|
|
257
|
+
var _a, _b;
|
|
258
|
+
const candidates = [(_a = asset === null || asset === void 0 ? void 0 : asset.metadata) === null || _a === void 0 ? void 0 : _a.decimals, (_b = asset === null || asset === void 0 ? void 0 : asset.onchain_metadata) === null || _b === void 0 ? void 0 : _b.decimals];
|
|
259
|
+
for (const value of candidates) {
|
|
260
|
+
if (value === undefined || value === null || value === '')
|
|
261
|
+
continue;
|
|
262
|
+
const n = Number(value);
|
|
263
|
+
if (Number.isInteger(n) && n >= 0)
|
|
264
|
+
return n;
|
|
265
|
+
}
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
/** `/assets/{assetId}/addresses`: every `{ address, quantity }` holding the asset (all pages). */
|
|
269
|
+
async function getAssetAddresses({ assetId, maxPages }) {
|
|
270
|
+
return blockfrostAll({ path: `/assets/${assetId}/addresses`, maxPages });
|
|
271
|
+
}
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// accounts
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
/** `/accounts/{stakeAddress}/addresses`: every payment address controlled by a stake key (all pages). */
|
|
276
|
+
async function getAccountAddresses({ stakeAddress, maxPages }) {
|
|
277
|
+
const rows = await blockfrostAll({ path: `/accounts/${stakeAddress}/addresses`, maxPages });
|
|
278
|
+
return rows.map(i => i.address);
|
|
279
|
+
}
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// blocks / epochs
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
function toBlock(raw) {
|
|
284
|
+
return { number: raw.height, timestamp: raw.time, hash: raw.hash, slot: raw.slot, epoch: raw.epoch, raw };
|
|
285
|
+
}
|
|
286
|
+
/** `/blocks/latest` as `{ number, timestamp, hash, slot, epoch, raw }`. */
|
|
287
|
+
async function getLatestBlock() {
|
|
288
|
+
return toBlock(await blockfrost({ path: '/blocks/latest' }));
|
|
289
|
+
}
|
|
290
|
+
/** `/blocks/{hashOrNumber}`; `null` when unknown. */
|
|
291
|
+
async function getBlock({ hashOrNumber }) {
|
|
292
|
+
const raw = await blockfrost({ path: `/blocks/${hashOrNumber}`, allowNotFound: true });
|
|
293
|
+
return raw ? toBlock(raw) : null;
|
|
294
|
+
}
|
|
295
|
+
/** `/blocks/slot/{slot}`; `null` when no block was minted in that slot. */
|
|
296
|
+
async function getBlockBySlot({ slot }) {
|
|
297
|
+
const raw = await blockfrost({ path: `/blocks/slot/${slot}`, allowNotFound: true });
|
|
298
|
+
return raw ? toBlock(raw) : null;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Last block with `time <= timestamp` (unix seconds). For Shelley-era timestamps the slot is
|
|
302
|
+
* derived directly (`slot = timestamp - SHELLEY_SLOT_OFFSET`) and `/blocks/slot/{slot}` is
|
|
303
|
+
* tried first; otherwise (or when that slot is empty) an interpolation search over block
|
|
304
|
+
* heights, seeded from the latest block, converges in a handful of requests.
|
|
305
|
+
*/
|
|
306
|
+
async function getBlockAtTimestamp({ timestamp }) {
|
|
307
|
+
if (!timestamp || !Number.isFinite(timestamp))
|
|
308
|
+
throw new Error('[chains.cardano] getBlockAtTimestamp: invalid timestamp');
|
|
309
|
+
if (timestamp > 1e12)
|
|
310
|
+
timestamp = Math.floor(timestamp / 1000); // tolerate milliseconds
|
|
311
|
+
const latest = await getLatestBlock();
|
|
312
|
+
if (latest.timestamp <= timestamp)
|
|
313
|
+
return latest;
|
|
314
|
+
let steps = 0;
|
|
315
|
+
if (timestamp >= exports.SHELLEY_START_TIME) {
|
|
316
|
+
const exact = await getBlockBySlot({ slot: timestamp - exports.SHELLEY_SLOT_OFFSET });
|
|
317
|
+
steps++;
|
|
318
|
+
if (exact && exact.timestamp === timestamp) {
|
|
319
|
+
(0, debugLog_1.debugLog)(`[chains.cardano] getBlockAtTimestamp(${timestamp}) -> block ${exact.number} via slot lookup`);
|
|
320
|
+
return exact;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const cache = new Map();
|
|
324
|
+
cache.set(latest.number, latest);
|
|
325
|
+
const fetchBlock = async (height) => {
|
|
326
|
+
if (!cache.has(height)) {
|
|
327
|
+
const block = await getBlock({ hashOrNumber: height });
|
|
328
|
+
if (!block)
|
|
329
|
+
throw new Error(`[chains.cardano] block ${height} not found`);
|
|
330
|
+
cache.set(height, block);
|
|
331
|
+
steps++;
|
|
332
|
+
}
|
|
333
|
+
return cache.get(height);
|
|
334
|
+
};
|
|
335
|
+
let hi = latest;
|
|
336
|
+
let lo = await fetchBlock(1);
|
|
337
|
+
if (lo.timestamp > timestamp)
|
|
338
|
+
throw new Error(`[chains.cardano] timestamp ${timestamp} predates the chain (block 1 at ${lo.timestamp})`);
|
|
339
|
+
// invariant: lo.timestamp <= timestamp < hi.timestamp
|
|
340
|
+
while (hi.number - lo.number > 1) {
|
|
341
|
+
const span = hi.timestamp - lo.timestamp;
|
|
342
|
+
let guess = span > 0
|
|
343
|
+
? lo.number + Math.floor((hi.number - lo.number) * (timestamp - lo.timestamp) / span)
|
|
344
|
+
: Math.floor((lo.number + hi.number) / 2);
|
|
345
|
+
// keep the guess strictly inside (lo, hi); alternate with bisection when interpolation stalls
|
|
346
|
+
if (guess <= lo.number || guess >= hi.number || steps % 4 === 3)
|
|
347
|
+
guess = Math.floor((lo.number + hi.number) / 2);
|
|
348
|
+
const block = await fetchBlock(guess);
|
|
349
|
+
if (block.timestamp <= timestamp)
|
|
350
|
+
lo = block;
|
|
351
|
+
else
|
|
352
|
+
hi = block;
|
|
353
|
+
if (steps > 200)
|
|
354
|
+
throw new Error(`[chains.cardano] getBlockAtTimestamp(${timestamp}): search did not converge`);
|
|
355
|
+
}
|
|
356
|
+
(0, debugLog_1.debugLog)(`[chains.cardano] getBlockAtTimestamp(${timestamp}) -> block ${lo.number} in ${steps} requests`);
|
|
357
|
+
return lo;
|
|
358
|
+
}
|
|
359
|
+
/** `/epochs/{epoch}` (or `/epochs/latest`). */
|
|
360
|
+
async function getEpoch({ epoch = 'latest' } = {}) {
|
|
361
|
+
return blockfrost({ path: `/epochs/${epoch}` });
|
|
362
|
+
}
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
// pure helpers
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
const POLICY_ID_LENGTH = 56;
|
|
367
|
+
const HEX_RE = /^([0-9a-fA-F]{2})*$/;
|
|
368
|
+
/** Split `<policyId><assetNameHex>` (or `lovelace`) into its parts. */
|
|
369
|
+
function parseAssetId(assetId) {
|
|
370
|
+
if (assetId === 'lovelace')
|
|
371
|
+
return { policyId: '', assetNameHex: '', assetName: 'lovelace' };
|
|
372
|
+
if (typeof assetId !== 'string' || assetId.length < POLICY_ID_LENGTH || !HEX_RE.test(assetId))
|
|
373
|
+
throw new Error(`[chains.cardano] invalid asset id: ${assetId}`);
|
|
374
|
+
const policyId = assetId.slice(0, POLICY_ID_LENGTH).toLowerCase();
|
|
375
|
+
const assetNameHex = assetId.slice(POLICY_ID_LENGTH).toLowerCase();
|
|
376
|
+
return { policyId, assetNameHex, assetName: decodeAssetName(assetNameHex) };
|
|
377
|
+
}
|
|
378
|
+
/** Hex asset name -> utf8 when it round-trips and is printable, else the hex itself. */
|
|
379
|
+
function decodeAssetName(assetNameHex) {
|
|
380
|
+
if (!assetNameHex)
|
|
381
|
+
return '';
|
|
382
|
+
const buf = Buffer.from(assetNameHex, 'hex');
|
|
383
|
+
const utf8 = buf.toString('utf8');
|
|
384
|
+
const roundTrips = Buffer.from(utf8, 'utf8').toString('hex') === assetNameHex.toLowerCase();
|
|
385
|
+
// printable: no control chars (0x00-0x1f, 0x7f) and no replacement char
|
|
386
|
+
const printable = roundTrips && !/[\u0000-\u001f\u007f�]/.test(utf8);
|
|
387
|
+
return printable ? utf8 : assetNameHex.toLowerCase();
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* `policyId + assetNameHex`. `assetName` is taken as hex when it is a valid even-length hex
|
|
391
|
+
* string, otherwise utf8 encoded; pass `encoding` to force one.
|
|
392
|
+
*/
|
|
393
|
+
function buildAssetId(policyId, assetName = '', encoding) {
|
|
394
|
+
if (typeof policyId !== 'string' || policyId.length !== POLICY_ID_LENGTH || !HEX_RE.test(policyId))
|
|
395
|
+
throw new Error(`[chains.cardano] invalid policy id: ${policyId}`);
|
|
396
|
+
const isHex = encoding === 'hex' || (encoding === undefined && HEX_RE.test(assetName));
|
|
397
|
+
const nameHex = isHex ? assetName.toLowerCase() : Buffer.from(assetName, 'utf8').toString('hex');
|
|
398
|
+
return policyId.toLowerCase() + nameHex;
|
|
399
|
+
}
|
|
400
|
+
/** `stake1...` (mainnet) / `stake_test1...` bech32 reward addresses. */
|
|
401
|
+
function isStakeAddress(address) {
|
|
402
|
+
return typeof address === 'string' && /^stake(_test)?1[a-z0-9]+$/.test(address);
|
|
403
|
+
}
|
|
404
|
+
/** lovelace (string | number | bigint) -> ADA as a number. */
|
|
405
|
+
function lovelaceToAda(lovelace) {
|
|
406
|
+
return Number(lovelace) / 1e6;
|
|
407
|
+
}
|