@ledgerhq/coin-tezos 7.12.0 → 8.0.0
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/CHANGELOG.md +28 -0
- package/lib/api/index.d.ts +1 -2
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +45 -44
- package/lib/api/index.js.map +1 -1
- package/lib/api/types.d.ts +3 -2
- package/lib/api/types.d.ts.map +1 -1
- package/lib/config.d.ts +3 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js.map +1 -1
- package/lib/logic/broadcast.d.ts +2 -1
- package/lib/logic/broadcast.d.ts.map +1 -1
- package/lib/logic/broadcast.js +3 -2
- package/lib/logic/broadcast.js.map +1 -1
- package/lib/logic/craftRawOperations.d.ts +2 -1
- package/lib/logic/craftRawOperations.d.ts.map +1 -1
- package/lib/logic/craftRawOperations.js +5 -8
- package/lib/logic/craftRawOperations.js.map +1 -1
- package/lib/logic/craftTransaction.d.ts +3 -2
- package/lib/logic/craftTransaction.d.ts.map +1 -1
- package/lib/logic/craftTransaction.js +6 -9
- package/lib/logic/craftTransaction.js.map +1 -1
- package/lib/logic/estimateFees.d.ts +2 -1
- package/lib/logic/estimateFees.d.ts.map +1 -1
- package/lib/logic/estimateFees.js +24 -19
- package/lib/logic/estimateFees.js.map +1 -1
- package/lib/logic/getBalance.d.ts +2 -1
- package/lib/logic/getBalance.d.ts.map +1 -1
- package/lib/logic/getBalance.js +7 -8
- package/lib/logic/getBalance.js.map +1 -1
- package/lib/logic/getBlock.d.ts +2 -1
- package/lib/logic/getBlock.d.ts.map +1 -1
- package/lib/logic/getBlock.js +12 -10
- package/lib/logic/getBlock.js.map +1 -1
- package/lib/logic/getBlockInfo.d.ts +2 -1
- package/lib/logic/getBlockInfo.d.ts.map +1 -1
- package/lib/logic/getBlockInfo.js +5 -3
- package/lib/logic/getBlockInfo.js.map +1 -1
- package/lib/logic/getStakes.d.ts +3 -2
- package/lib/logic/getStakes.d.ts.map +1 -1
- package/lib/logic/getStakes.js +9 -9
- package/lib/logic/getStakes.js.map +1 -1
- package/lib/logic/lastBlock.d.ts +2 -1
- package/lib/logic/lastBlock.d.ts.map +1 -1
- package/lib/logic/lastBlock.js +4 -6
- package/lib/logic/lastBlock.js.map +1 -1
- package/lib/logic/listOperations.d.ts +2 -1
- package/lib/logic/listOperations.d.ts.map +1 -1
- package/lib/logic/listOperations.js +8 -6
- package/lib/logic/listOperations.js.map +1 -1
- package/lib/logic/tezosToolkit.d.ts +2 -1
- package/lib/logic/tezosToolkit.d.ts.map +1 -1
- package/lib/logic/tezosToolkit.js +2 -6
- package/lib/logic/tezosToolkit.js.map +1 -1
- package/lib/logic/validateIntent.d.ts +2 -1
- package/lib/logic/validateIntent.d.ts.map +1 -1
- package/lib/logic/validateIntent.js +12 -13
- package/lib/logic/validateIntent.js.map +1 -1
- package/lib/network/bakers.d.ts +5 -4
- package/lib/network/bakers.d.ts.map +1 -1
- package/lib/network/bakers.js +9 -10
- package/lib/network/bakers.js.map +1 -1
- package/lib/network/index.d.ts +1 -1
- package/lib/network/index.d.ts.map +1 -1
- package/lib/network/index.js +2 -5
- package/lib/network/index.js.map +1 -1
- package/lib/network/tzkt.d.ts +16 -9
- package/lib/network/tzkt.d.ts.map +1 -1
- package/lib/network/tzkt.js +341 -333
- package/lib/network/tzkt.js.map +1 -1
- package/lib/utils.d.ts +2 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +1 -6
- package/lib/utils.js.map +1 -1
- package/lib-es/api/index.d.ts +1 -2
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +40 -36
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/api/types.d.ts +3 -2
- package/lib-es/api/types.d.ts.map +1 -1
- package/lib-es/config.d.ts +3 -1
- package/lib-es/config.d.ts.map +1 -1
- package/lib-es/config.js.map +1 -1
- package/lib-es/logic/broadcast.d.ts +2 -1
- package/lib-es/logic/broadcast.d.ts.map +1 -1
- package/lib-es/logic/broadcast.js +3 -2
- package/lib-es/logic/broadcast.js.map +1 -1
- package/lib-es/logic/craftRawOperations.d.ts +2 -1
- package/lib-es/logic/craftRawOperations.d.ts.map +1 -1
- package/lib-es/logic/craftRawOperations.js +5 -5
- package/lib-es/logic/craftRawOperations.js.map +1 -1
- package/lib-es/logic/craftTransaction.d.ts +3 -2
- package/lib-es/logic/craftTransaction.d.ts.map +1 -1
- package/lib-es/logic/craftTransaction.js +6 -6
- package/lib-es/logic/craftTransaction.js.map +1 -1
- package/lib-es/logic/estimateFees.d.ts +2 -1
- package/lib-es/logic/estimateFees.d.ts.map +1 -1
- package/lib-es/logic/estimateFees.js +24 -16
- package/lib-es/logic/estimateFees.js.map +1 -1
- package/lib-es/logic/getBalance.d.ts +2 -1
- package/lib-es/logic/getBalance.d.ts.map +1 -1
- package/lib-es/logic/getBalance.js +5 -3
- package/lib-es/logic/getBalance.js.map +1 -1
- package/lib-es/logic/getBlock.d.ts +2 -1
- package/lib-es/logic/getBlock.d.ts.map +1 -1
- package/lib-es/logic/getBlock.js +10 -8
- package/lib-es/logic/getBlock.js.map +1 -1
- package/lib-es/logic/getBlockInfo.d.ts +2 -1
- package/lib-es/logic/getBlockInfo.d.ts.map +1 -1
- package/lib-es/logic/getBlockInfo.js +4 -2
- package/lib-es/logic/getBlockInfo.js.map +1 -1
- package/lib-es/logic/getStakes.d.ts +3 -2
- package/lib-es/logic/getStakes.d.ts.map +1 -1
- package/lib-es/logic/getStakes.js +9 -6
- package/lib-es/logic/getStakes.js.map +1 -1
- package/lib-es/logic/lastBlock.d.ts +2 -1
- package/lib-es/logic/lastBlock.d.ts.map +1 -1
- package/lib-es/logic/lastBlock.js +4 -3
- package/lib-es/logic/lastBlock.js.map +1 -1
- package/lib-es/logic/listOperations.d.ts +2 -1
- package/lib-es/logic/listOperations.d.ts.map +1 -1
- package/lib-es/logic/listOperations.js +7 -5
- package/lib-es/logic/listOperations.js.map +1 -1
- package/lib-es/logic/tezosToolkit.d.ts +2 -1
- package/lib-es/logic/tezosToolkit.d.ts.map +1 -1
- package/lib-es/logic/tezosToolkit.js +2 -3
- package/lib-es/logic/tezosToolkit.js.map +1 -1
- package/lib-es/logic/validateIntent.d.ts +2 -1
- package/lib-es/logic/validateIntent.d.ts.map +1 -1
- package/lib-es/logic/validateIntent.js +10 -8
- package/lib-es/logic/validateIntent.js.map +1 -1
- package/lib-es/network/bakers.d.ts +5 -4
- package/lib-es/network/bakers.d.ts.map +1 -1
- package/lib-es/network/bakers.js +9 -10
- package/lib-es/network/bakers.js.map +1 -1
- package/lib-es/network/index.d.ts +1 -1
- package/lib-es/network/index.d.ts.map +1 -1
- package/lib-es/network/index.js +1 -1
- package/lib-es/network/index.js.map +1 -1
- package/lib-es/network/tzkt.d.ts +16 -9
- package/lib-es/network/tzkt.d.ts.map +1 -1
- package/lib-es/network/tzkt.js +340 -333
- package/lib-es/network/tzkt.js.map +1 -1
- package/lib-es/utils.d.ts +2 -1
- package/lib-es/utils.d.ts.map +1 -1
- package/lib-es/utils.js +1 -3
- package/lib-es/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/api/index-mainnet.integ.test.ts +34 -30
- package/src/api/index.integ.test.ts +69 -53
- package/src/api/index.test.ts +99 -60
- package/src/api/index.ts +52 -36
- package/src/api/types.ts +3 -2
- package/src/config.ts +4 -1
- package/src/logic/broadcast.integ.test.ts +12 -13
- package/src/logic/broadcast.ts +4 -2
- package/src/logic/craftRawOperations.test.ts +47 -33
- package/src/logic/craftRawOperations.ts +6 -4
- package/src/logic/craftTransaction.integ.test.ts +12 -5
- package/src/logic/craftTransaction.test.ts +39 -31
- package/src/logic/craftTransaction.ts +10 -5
- package/src/logic/estimateFees.integ.test.ts +7 -7
- package/src/logic/estimateFees.test.ts +52 -49
- package/src/logic/estimateFees.ts +34 -22
- package/src/logic/getBalance.test.ts +19 -17
- package/src/logic/getBalance.ts +6 -3
- package/src/logic/getBlock.integ.test.ts +8 -30
- package/src/logic/getBlock.test.ts +91 -81
- package/src/logic/getBlock.ts +11 -8
- package/src/logic/getBlockInfo.integ.test.ts +9 -18
- package/src/logic/getBlockInfo.test.ts +19 -9
- package/src/logic/getBlockInfo.ts +5 -2
- package/src/logic/getStakes.test.ts +46 -49
- package/src/logic/getStakes.ts +14 -5
- package/src/logic/lastBlock.ts +5 -3
- package/src/logic/listOperations.integ.test.ts +40 -67
- package/src/logic/listOperations.test.ts +65 -59
- package/src/logic/listOperations.ts +8 -3
- package/src/logic/tezosToolkit.ts +3 -3
- package/src/logic/validateIntent.test.ts +78 -74
- package/src/logic/validateIntent.ts +17 -7
- package/src/network/bakers.integ.test.ts +2 -5
- package/src/network/bakers.test.ts +5 -6
- package/src/network/bakers.ts +13 -10
- package/src/network/index.ts +2 -1
- package/src/network/tzkt.integ.test.ts +3 -14
- package/src/network/tzkt.test.ts +128 -96
- package/src/network/tzkt.ts +426 -393
- package/src/utils.ts +2 -3
package/src/network/tzkt.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
import type { TezosCoinConfig } from '../config'
|
|
4
5
|
import type {
|
|
5
6
|
APIAccount,
|
|
6
7
|
APIBlock,
|
|
@@ -19,7 +20,6 @@ import type {
|
|
|
19
20
|
import URL from 'url'
|
|
20
21
|
import network from '@ledgerhq/live-network'
|
|
21
22
|
import { log } from '@ledgerhq/logs'
|
|
22
|
-
import coinConfig from '../config'
|
|
23
23
|
|
|
24
24
|
/** TzKT hard-caps `limit` at 10 000; we use a safer page size to stay well under that. */
|
|
25
25
|
const BLOCK_PAGE_SIZE = 1000
|
|
@@ -27,8 +27,6 @@ const BLOCK_PAGE_SIZE = 1000
|
|
|
27
27
|
/** Maximum number of IDs per `id.in` request to keep URLs under the Cloudflare proxy limit (~16 KB). */
|
|
28
28
|
const ID_IN_CHUNK_SIZE = 100
|
|
29
29
|
|
|
30
|
-
const getExplorerUrl = () => coinConfig.getCoinConfig().explorer.url
|
|
31
|
-
|
|
32
30
|
/**
|
|
33
31
|
* Coalesces concurrent `getAccountByAddress` calls for the same explorer+address into a
|
|
34
32
|
* single request. During a sync both `getBalance` and the readiness hook hit this endpoint
|
|
@@ -53,412 +51,428 @@ function chunk<T>(arr: T[], size: number): T[][] {
|
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
54
|
+
* Builds the TzKT explorer client bound to the given coin config (ADR-019). The caller resolves the
|
|
55
|
+
* config from its {@link Context} and passes it in, rather than the client reading a module-level
|
|
56
|
+
* singleton.
|
|
58
57
|
*/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
level: number,
|
|
62
|
-
cursor?: number,
|
|
63
|
-
apiQueryParams: Record<string, unknown> = {}
|
|
64
|
-
): Promise<(APITransactionType & { block: string; hash: string })[]> {
|
|
65
|
-
// "sort.asc": "id" guarantees forward progress for cursor-based paging (offset.cr).
|
|
66
|
-
// Without an explicit sort the API default may be descending, which would cause the
|
|
67
|
-
// cursor to go backwards and produce duplicates or an infinite loop.
|
|
68
|
-
const params: Record<string, unknown> = {
|
|
69
|
-
'level.gte': level,
|
|
70
|
-
limit: BLOCK_PAGE_SIZE,
|
|
71
|
-
'sort.asc': 'id',
|
|
72
|
-
...clearUndefined(apiQueryParams),
|
|
73
|
-
}
|
|
74
|
-
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
75
|
-
const { data } = await network<(APITransactionType & { block: string; hash: string })[]>({
|
|
76
|
-
url: `${getExplorerUrl()}/v1/operations/${type}`,
|
|
77
|
-
params,
|
|
78
|
-
})
|
|
79
|
-
return data
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const api = {
|
|
83
|
-
async getBlockCount(): Promise<number> {
|
|
84
|
-
const { data } = await network<number>({
|
|
85
|
-
url: `${getExplorerUrl()}/v1/blocks/count`,
|
|
86
|
-
})
|
|
87
|
-
return data
|
|
88
|
-
},
|
|
89
|
-
async getLastBlock(): Promise<{ hash: string; level: number; date: Date }> {
|
|
90
|
-
const { data } = await network<APIBlock[]>({
|
|
91
|
-
url: `${getExplorerUrl()}/v1/blocks`,
|
|
92
|
-
params: {
|
|
93
|
-
'sort.desc': 'level',
|
|
94
|
-
},
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
hash: data[0].hash,
|
|
99
|
-
level: data[0].level,
|
|
100
|
-
date: new Date(data[0].timestamp),
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
async getAccountByAddress(address: string): Promise<APIAccount> {
|
|
104
|
-
const key = `${getExplorerUrl()}|${address}`
|
|
105
|
-
const existing = accountByAddressInflight.get(key)
|
|
106
|
-
if (existing) return existing
|
|
107
|
-
const request = network<APIAccount>({
|
|
108
|
-
url: `${getExplorerUrl()}/v1/accounts/${address}`,
|
|
109
|
-
})
|
|
110
|
-
.then(({ data }) => data)
|
|
111
|
-
.finally(() => accountByAddressInflight.delete(key))
|
|
112
|
-
accountByAddressInflight.set(key, request)
|
|
113
|
-
return request
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Returns the total `actualAmount` (mutez) summed over the account's `finalizable`
|
|
118
|
-
* unstake requests — the portion of `unstakedBalance` whose unlock cycle has been
|
|
119
|
-
* reached and that can be reclaimed via `finalize_unstake`. The complementary
|
|
120
|
-
* `unstakedBalance - finalizable` portion is still in the deactivation delay window.
|
|
121
|
-
*
|
|
122
|
-
* TzKT's account endpoint does not expose this split, so we sum from
|
|
123
|
-
* `/v1/staking/unstake_requests`. https://api.tzkt.io/#operation/Staking_GetUnstakeRequests
|
|
124
|
-
*
|
|
125
|
-
* `limit=1000` covers any realistic number of concurrent finalizable requests for
|
|
126
|
-
* a single staker — we do not paginate.
|
|
127
|
-
*/
|
|
128
|
-
async getUnstakeRequestsFinalizable(address: string): Promise<bigint> {
|
|
129
|
-
const { data } = await network<number[]>({
|
|
130
|
-
url: `${getExplorerUrl()}/v1/staking/unstake_requests`,
|
|
131
|
-
params: {
|
|
132
|
-
'staker.eq': address,
|
|
133
|
-
status: 'finalizable',
|
|
134
|
-
'select.values': 'actualAmount',
|
|
135
|
-
limit: 1000,
|
|
136
|
-
},
|
|
137
|
-
})
|
|
138
|
-
return data.reduce<bigint>((sum, n) => sum + BigInt(n), 0n)
|
|
139
|
-
},
|
|
58
|
+
export function createTzktApi(config: TezosCoinConfig) {
|
|
59
|
+
const explorerUrl = config.explorer.url
|
|
140
60
|
|
|
141
61
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* https://api.tzkt.io/#operation/Staking_GetUnstakeRequests
|
|
62
|
+
* Internal helper shared by `getOperationsTransactions` and `getOperationsOrigination`.
|
|
63
|
+
* Both endpoints accept the same query shape; only the URL path differs.
|
|
145
64
|
*/
|
|
146
|
-
async
|
|
147
|
-
|
|
148
|
-
url: `${getExplorerUrl()}/v1/staking/unstake_requests`,
|
|
149
|
-
params: {
|
|
150
|
-
'staker.eq': address,
|
|
151
|
-
'status.ne': 'finalized',
|
|
152
|
-
'sort.asc': 'id',
|
|
153
|
-
limit: 1000,
|
|
154
|
-
},
|
|
155
|
-
})
|
|
156
|
-
return data
|
|
157
|
-
},
|
|
158
|
-
// https://api.tzkt.io/#operation/Accounts_GetOperations
|
|
159
|
-
async getAccountOperations(
|
|
160
|
-
address: string,
|
|
161
|
-
query: AccountsGetOperationsOptions
|
|
162
|
-
): Promise<APIOperation[]> {
|
|
163
|
-
// Remove undefined from query
|
|
164
|
-
Object.entries(query).forEach(
|
|
165
|
-
([key, value]) => value === undefined && delete query[key as keyof typeof query]
|
|
166
|
-
)
|
|
167
|
-
const { data } = await network<APIOperation[]>({
|
|
168
|
-
url: URL.format({
|
|
169
|
-
pathname: `${getExplorerUrl()}/v1/accounts/${address}/operations`,
|
|
170
|
-
query,
|
|
171
|
-
}),
|
|
172
|
-
})
|
|
173
|
-
return data
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
// https://api.tzkt.io/#operation/Blocks_GetByLevel
|
|
177
|
-
async getBlockByLevel(level: number): Promise<APIBlock> {
|
|
178
|
-
const { data } = await network<APIBlock>({
|
|
179
|
-
url: `${getExplorerUrl()}/v1/blocks/${level}`,
|
|
180
|
-
})
|
|
181
|
-
return data
|
|
182
|
-
},
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Resolves block hashes for the given levels in a single request.
|
|
186
|
-
* Uses `/v1/blocks?level.in=...&select.values=level,hash`, which TzKT honours
|
|
187
|
-
* (unlike `/v1/blocks/{level}?select=...`, where `select` is ignored). Used
|
|
188
|
-
* for cheap backfill of `block.hash` on operations whose level is known but
|
|
189
|
-
* whose response omits the block field (e.g. `/accounts/{addr}/operations`
|
|
190
|
-
* for staking ops). Levels that don't resolve are absent from the result map.
|
|
191
|
-
*/
|
|
192
|
-
async getBlockHashesByLevels(levels: readonly number[]): Promise<Map<number, string>> {
|
|
193
|
-
if (levels.length === 0) return new Map()
|
|
194
|
-
const { data } = await network<[number, string][]>({
|
|
195
|
-
url: `${getExplorerUrl()}/v1/blocks`,
|
|
196
|
-
params: {
|
|
197
|
-
'level.in': levels.join(','),
|
|
198
|
-
'select.values': 'level,hash',
|
|
199
|
-
limit: levels.length,
|
|
200
|
-
},
|
|
201
|
-
})
|
|
202
|
-
return new Map(data)
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Fetches a single page of `transaction` operations at the given block level.
|
|
207
|
-
* Internal — used by `fetchBlockTransactions` which handles pagination.
|
|
208
|
-
* https://api.tzkt.io/#operation/Operations_GetTransactions
|
|
209
|
-
*/
|
|
210
|
-
async getBlockTransactionsPage(level: number, cursor?: number): Promise<APITransactionType[]> {
|
|
211
|
-
// "sort.asc": "id" guarantees forward progress for cursor-based paging (offset.cr).
|
|
212
|
-
// Without an explicit sort the API default may be descending, which would cause the
|
|
213
|
-
// cursor to go backwards and produce duplicates or an infinite loop.
|
|
214
|
-
const params: Record<string, unknown> = { level, limit: BLOCK_PAGE_SIZE, 'sort.asc': 'id' }
|
|
215
|
-
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
216
|
-
const { data } = await network<APITransactionType[]>({
|
|
217
|
-
url: `${getExplorerUrl()}/v1/operations/transactions`,
|
|
218
|
-
params,
|
|
219
|
-
})
|
|
220
|
-
return data
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Fetches a list of `transaction` operations after the given level.
|
|
225
|
-
* https://api.tzkt.io/#operation/Operations_GetTransactions
|
|
226
|
-
*/
|
|
227
|
-
async getOperationsTransactions(
|
|
228
|
-
level: number,
|
|
229
|
-
cursor?: number,
|
|
230
|
-
apiQueryParams: Record<string, unknown> = {}
|
|
231
|
-
): Promise<(APITransactionType & { block: string; hash: string })[]> {
|
|
232
|
-
return getOperationsByType('transactions', level, cursor, apiQueryParams)
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Fetches a list of `originations` operations after the given level.
|
|
237
|
-
* https://api.tzkt.io/#operation/Operations_GetOriginations
|
|
238
|
-
*/
|
|
239
|
-
async getOperationsOrigination(
|
|
65
|
+
async function getOperationsByType(
|
|
66
|
+
type: 'transactions' | 'originations',
|
|
240
67
|
level: number,
|
|
241
68
|
cursor?: number,
|
|
242
69
|
apiQueryParams: Record<string, unknown> = {}
|
|
243
70
|
): Promise<(APITransactionType & { block: string; hash: string })[]> {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Fetches a single page of FA token transfers at the given block level.
|
|
249
|
-
* Internal — used by `fetchBlockTokenTransfers` which handles pagination.
|
|
250
|
-
* https://api.tzkt.io/#operation/Tokens_GetTokenTransfers
|
|
251
|
-
*/
|
|
252
|
-
async getBlockTokenTransfersPage(level: number, cursor?: number): Promise<APITokenTransfer[]> {
|
|
253
|
-
// Same rationale as getBlockTransactionsPage: explicit ascending sort keeps the
|
|
254
|
-
// offset.cr cursor advancing forward regardless of the API's default ordering.
|
|
71
|
+
// "sort.asc": "id" guarantees forward progress for cursor-based paging (offset.cr).
|
|
72
|
+
// Without an explicit sort the API default may be descending, which would cause the
|
|
73
|
+
// cursor to go backwards and produce duplicates or an infinite loop.
|
|
255
74
|
const params: Record<string, unknown> = {
|
|
256
|
-
level,
|
|
75
|
+
'level.gte': level,
|
|
257
76
|
limit: BLOCK_PAGE_SIZE,
|
|
258
77
|
'sort.asc': 'id',
|
|
259
|
-
'token.standard': 'fa2',
|
|
260
|
-
}
|
|
261
|
-
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
262
|
-
const { data } = await network<APITokenTransfer[]>({
|
|
263
|
-
url: `${getExplorerUrl()}/v1/tokens/transfers`,
|
|
264
|
-
params,
|
|
265
|
-
})
|
|
266
|
-
return data
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Fetches the latest FA token transfers since the given level.
|
|
271
|
-
* https://api.tzkt.io/#operation/Tokens_GetTokenTransfers
|
|
272
|
-
*/
|
|
273
|
-
async getTokenTransfers(
|
|
274
|
-
apiQueryParams: Record<string, unknown> = {}
|
|
275
|
-
): Promise<APITokenTransfer[]> {
|
|
276
|
-
const params = {
|
|
277
78
|
...clearUndefined(apiQueryParams),
|
|
278
79
|
}
|
|
279
|
-
const { data } = await network<APITokenTransfer[]>({
|
|
280
|
-
url: `${getExplorerUrl()}/v1/tokens/transfers`,
|
|
281
|
-
params,
|
|
282
|
-
})
|
|
283
|
-
return data
|
|
284
|
-
},
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Fetches a single page of `origination` operations at the given block level.
|
|
288
|
-
* Internal — used by `fetchBlockOriginations` which handles pagination.
|
|
289
|
-
* https://api.tzkt.io/#operation/Operations_GetOriginations
|
|
290
|
-
*/
|
|
291
|
-
async getBlockOriginationsPage(level: number, cursor?: number): Promise<APIOriginationType[]> {
|
|
292
|
-
const params: Record<string, unknown> = { level, limit: BLOCK_PAGE_SIZE, 'sort.asc': 'id' }
|
|
293
|
-
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
294
|
-
const { data } = await network<APIOriginationType[]>({
|
|
295
|
-
url: `${getExplorerUrl()}/v1/operations/originations`,
|
|
296
|
-
params,
|
|
297
|
-
})
|
|
298
|
-
return data
|
|
299
|
-
},
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Fetches a single page of `reveal` operations at the given block level.
|
|
303
|
-
* Internal — used by `fetchBlockReveals` which handles pagination.
|
|
304
|
-
* https://api.tzkt.io/#operation/Operations_GetReveals
|
|
305
|
-
*/
|
|
306
|
-
async getBlockRevealsPage(level: number, cursor?: number): Promise<APIRevealType[]> {
|
|
307
|
-
const params: Record<string, unknown> = { level, limit: BLOCK_PAGE_SIZE, 'sort.asc': 'id' }
|
|
308
|
-
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
309
|
-
const { data } = await network<APIRevealType[]>({
|
|
310
|
-
url: `${getExplorerUrl()}/v1/operations/reveals`,
|
|
311
|
-
params,
|
|
312
|
-
})
|
|
313
|
-
return data
|
|
314
|
-
},
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Fetches a single page of `delegation` operations at the given block level.
|
|
318
|
-
* Internal — used by `fetchBlockDelegations` which handles pagination.
|
|
319
|
-
* https://api.tzkt.io/#operation/Operations_GetDelegations
|
|
320
|
-
*/
|
|
321
|
-
async getBlockDelegationsPage(level: number, cursor?: number): Promise<APIDelegationType[]> {
|
|
322
|
-
const params: Record<string, unknown> = { level, limit: BLOCK_PAGE_SIZE, 'sort.asc': 'id' }
|
|
323
80
|
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
324
|
-
const { data } = await network<
|
|
325
|
-
url: `${
|
|
81
|
+
const { data } = await network<(APITransactionType & { block: string; hash: string })[]>({
|
|
82
|
+
url: `${explorerUrl}/v1/operations/${type}`,
|
|
326
83
|
params,
|
|
327
84
|
})
|
|
328
85
|
return data
|
|
329
|
-
}
|
|
86
|
+
}
|
|
330
87
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
88
|
+
const api = {
|
|
89
|
+
async getBlockCount(): Promise<number> {
|
|
90
|
+
const { data } = await network<number>({
|
|
91
|
+
url: `${explorerUrl}/v1/blocks/count`,
|
|
92
|
+
})
|
|
93
|
+
return data
|
|
94
|
+
},
|
|
95
|
+
async getLastBlock(): Promise<{ hash: string; level: number; date: Date }> {
|
|
96
|
+
const { data } = await network<APIBlock[]>({
|
|
97
|
+
url: `${explorerUrl}/v1/blocks`,
|
|
98
|
+
params: {
|
|
99
|
+
'sort.desc': 'level',
|
|
100
|
+
},
|
|
101
|
+
})
|
|
345
102
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
103
|
+
return {
|
|
104
|
+
hash: data[0].hash,
|
|
105
|
+
level: data[0].level,
|
|
106
|
+
date: new Date(data[0].timestamp),
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
async getAccountByAddress(address: string): Promise<APIAccount> {
|
|
110
|
+
const key = `${explorerUrl}|${address}`
|
|
111
|
+
const existing = accountByAddressInflight.get(key)
|
|
112
|
+
if (existing) return existing
|
|
113
|
+
const request = network<APIAccount>({
|
|
114
|
+
url: `${explorerUrl}/v1/accounts/${address}`,
|
|
115
|
+
})
|
|
116
|
+
.then(({ data }) => data)
|
|
117
|
+
.finally(() => accountByAddressInflight.delete(key))
|
|
118
|
+
accountByAddressInflight.set(key, request)
|
|
119
|
+
return request
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns the total `actualAmount` (mutez) summed over the account's `finalizable`
|
|
124
|
+
* unstake requests — the portion of `unstakedBalance` whose unlock cycle has been
|
|
125
|
+
* reached and that can be reclaimed via `finalize_unstake`. The complementary
|
|
126
|
+
* `unstakedBalance - finalizable` portion is still in the deactivation delay window.
|
|
127
|
+
*
|
|
128
|
+
* TzKT's account endpoint does not expose this split, so we sum from
|
|
129
|
+
* `/v1/staking/unstake_requests`. https://api.tzkt.io/#operation/Staking_GetUnstakeRequests
|
|
130
|
+
*
|
|
131
|
+
* `limit=1000` covers any realistic number of concurrent finalizable requests for
|
|
132
|
+
* a single staker — we do not paginate.
|
|
133
|
+
*/
|
|
134
|
+
async getUnstakeRequestsFinalizable(address: string): Promise<bigint> {
|
|
135
|
+
const { data } = await network<number[]>({
|
|
136
|
+
url: `${explorerUrl}/v1/staking/unstake_requests`,
|
|
137
|
+
params: {
|
|
138
|
+
'staker.eq': address,
|
|
139
|
+
status: 'finalizable',
|
|
140
|
+
'select.values': 'actualAmount',
|
|
141
|
+
limit: 1000,
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
return data.reduce<bigint>((sum, n) => sum + BigInt(n), 0n)
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Pending + finalizable unstake requests, by id ascending. Uses `status.ne=finalized` because
|
|
149
|
+
* TzKT ignores `status.in=pending,finalizable` on this endpoint and returns finalized requests.
|
|
150
|
+
* https://api.tzkt.io/#operation/Staking_GetUnstakeRequests
|
|
151
|
+
*/
|
|
152
|
+
async getUnstakeRequests(address: string): Promise<APIUnstakeRequest[]> {
|
|
153
|
+
const { data } = await network<APIUnstakeRequest[]>({
|
|
154
|
+
url: `${explorerUrl}/v1/staking/unstake_requests`,
|
|
155
|
+
params: {
|
|
156
|
+
'staker.eq': address,
|
|
157
|
+
'status.ne': 'finalized',
|
|
158
|
+
'sort.asc': 'id',
|
|
159
|
+
limit: 1000,
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
return data
|
|
163
|
+
},
|
|
164
|
+
// https://api.tzkt.io/#operation/Accounts_GetOperations
|
|
165
|
+
async getAccountOperations(
|
|
166
|
+
address: string,
|
|
167
|
+
query: AccountsGetOperationsOptions
|
|
168
|
+
): Promise<APIOperation[]> {
|
|
169
|
+
// Remove undefined from query
|
|
170
|
+
Object.entries(query).forEach(
|
|
171
|
+
([key, value]) => value === undefined && delete query[key as keyof typeof query]
|
|
172
|
+
)
|
|
173
|
+
const { data } = await network<APIOperation[]>({
|
|
174
|
+
url: URL.format({
|
|
175
|
+
pathname: `${explorerUrl}/v1/accounts/${address}/operations`,
|
|
176
|
+
query,
|
|
177
|
+
}),
|
|
178
|
+
})
|
|
179
|
+
return data
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// https://api.tzkt.io/#operation/Blocks_GetByLevel
|
|
183
|
+
async getBlockByLevel(level: number): Promise<APIBlock> {
|
|
184
|
+
const { data } = await network<APIBlock>({
|
|
185
|
+
url: `${explorerUrl}/v1/blocks/${level}`,
|
|
186
|
+
})
|
|
187
|
+
return data
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Resolves block hashes for the given levels in a single request.
|
|
192
|
+
* Uses `/v1/blocks?level.in=...&select.values=level,hash`, which TzKT honours
|
|
193
|
+
* (unlike `/v1/blocks/{level}?select=...`, where `select` is ignored). Used
|
|
194
|
+
* for cheap backfill of `block.hash` on operations whose level is known but
|
|
195
|
+
* whose response omits the block field (e.g. `/accounts/{addr}/operations`
|
|
196
|
+
* for staking ops). Levels that don't resolve are absent from the result map.
|
|
197
|
+
*/
|
|
198
|
+
async getBlockHashesByLevels(levels: readonly number[]): Promise<Map<number, string>> {
|
|
199
|
+
if (levels.length === 0) return new Map()
|
|
200
|
+
const { data } = await network<[number, string][]>({
|
|
201
|
+
url: `${explorerUrl}/v1/blocks`,
|
|
202
|
+
params: {
|
|
203
|
+
'level.in': levels.join(','),
|
|
204
|
+
'select.values': 'level,hash',
|
|
205
|
+
limit: levels.length,
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
return new Map(data)
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Fetches a single page of `transaction` operations at the given block level.
|
|
213
|
+
* Internal — used by `fetchBlockTransactions` which handles pagination.
|
|
214
|
+
* https://api.tzkt.io/#operation/Operations_GetTransactions
|
|
215
|
+
*/
|
|
216
|
+
async getBlockTransactionsPage(level: number, cursor?: number): Promise<APITransactionType[]> {
|
|
217
|
+
// "sort.asc": "id" guarantees forward progress for cursor-based paging (offset.cr).
|
|
218
|
+
// Without an explicit sort the API default may be descending, which would cause the
|
|
219
|
+
// cursor to go backwards and produce duplicates or an infinite loop.
|
|
220
|
+
const params: Record<string, unknown> = { level, limit: BLOCK_PAGE_SIZE, 'sort.asc': 'id' }
|
|
221
|
+
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
222
|
+
const { data } = await network<APITransactionType[]>({
|
|
223
|
+
url: `${explorerUrl}/v1/operations/transactions`,
|
|
224
|
+
params,
|
|
225
|
+
})
|
|
226
|
+
return data
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Fetches a list of `transaction` operations after the given level.
|
|
231
|
+
* https://api.tzkt.io/#operation/Operations_GetTransactions
|
|
232
|
+
*/
|
|
233
|
+
async getOperationsTransactions(
|
|
234
|
+
level: number,
|
|
235
|
+
cursor?: number,
|
|
236
|
+
apiQueryParams: Record<string, unknown> = {}
|
|
237
|
+
): Promise<(APITransactionType & { block: string; hash: string })[]> {
|
|
238
|
+
return getOperationsByType('transactions', level, cursor, apiQueryParams)
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Fetches a list of `originations` operations after the given level.
|
|
243
|
+
* https://api.tzkt.io/#operation/Operations_GetOriginations
|
|
244
|
+
*/
|
|
245
|
+
async getOperationsOrigination(
|
|
246
|
+
level: number,
|
|
247
|
+
cursor?: number,
|
|
248
|
+
apiQueryParams: Record<string, unknown> = {}
|
|
249
|
+
): Promise<(APITransactionType & { block: string; hash: string })[]> {
|
|
250
|
+
return getOperationsByType('originations', level, cursor, apiQueryParams)
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Fetches a single page of FA token transfers at the given block level.
|
|
255
|
+
* Internal — used by `fetchBlockTokenTransfers` which handles pagination.
|
|
256
|
+
* https://api.tzkt.io/#operation/Tokens_GetTokenTransfers
|
|
257
|
+
*/
|
|
258
|
+
async getBlockTokenTransfersPage(level: number, cursor?: number): Promise<APITokenTransfer[]> {
|
|
259
|
+
// Same rationale as getBlockTransactionsPage: explicit ascending sort keeps the
|
|
260
|
+
// offset.cr cursor advancing forward regardless of the API's default ordering.
|
|
261
|
+
const params: Record<string, unknown> = {
|
|
262
|
+
level,
|
|
263
|
+
limit: BLOCK_PAGE_SIZE,
|
|
264
|
+
'sort.asc': 'id',
|
|
265
|
+
'token.standard': 'fa2',
|
|
266
|
+
}
|
|
267
|
+
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
268
|
+
const { data } = await network<APITokenTransfer[]>({
|
|
269
|
+
url: `${explorerUrl}/v1/tokens/transfers`,
|
|
270
|
+
params,
|
|
271
|
+
})
|
|
272
|
+
return data
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Fetches the latest FA token transfers since the given level.
|
|
277
|
+
* https://api.tzkt.io/#operation/Tokens_GetTokenTransfers
|
|
278
|
+
*/
|
|
279
|
+
async getTokenTransfers(
|
|
280
|
+
apiQueryParams: Record<string, unknown> = {}
|
|
281
|
+
): Promise<APITokenTransfer[]> {
|
|
282
|
+
const params = {
|
|
283
|
+
...clearUndefined(apiQueryParams),
|
|
284
|
+
}
|
|
285
|
+
const { data } = await network<APITokenTransfer[]>({
|
|
286
|
+
url: `${explorerUrl}/v1/tokens/transfers`,
|
|
287
|
+
params,
|
|
288
|
+
})
|
|
289
|
+
return data
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Fetches a single page of `origination` operations at the given block level.
|
|
294
|
+
* Internal — used by `fetchBlockOriginations` which handles pagination.
|
|
295
|
+
* https://api.tzkt.io/#operation/Operations_GetOriginations
|
|
296
|
+
*/
|
|
297
|
+
async getBlockOriginationsPage(level: number, cursor?: number): Promise<APIOriginationType[]> {
|
|
298
|
+
const params: Record<string, unknown> = { level, limit: BLOCK_PAGE_SIZE, 'sort.asc': 'id' }
|
|
299
|
+
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
300
|
+
const { data } = await network<APIOriginationType[]>({
|
|
301
|
+
url: `${explorerUrl}/v1/operations/originations`,
|
|
302
|
+
params,
|
|
303
|
+
})
|
|
304
|
+
return data
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Fetches a single page of `reveal` operations at the given block level.
|
|
309
|
+
* Internal — used by `fetchBlockReveals` which handles pagination.
|
|
310
|
+
* https://api.tzkt.io/#operation/Operations_GetReveals
|
|
311
|
+
*/
|
|
312
|
+
async getBlockRevealsPage(level: number, cursor?: number): Promise<APIRevealType[]> {
|
|
313
|
+
const params: Record<string, unknown> = { level, limit: BLOCK_PAGE_SIZE, 'sort.asc': 'id' }
|
|
314
|
+
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
315
|
+
const { data } = await network<APIRevealType[]>({
|
|
316
|
+
url: `${explorerUrl}/v1/operations/reveals`,
|
|
317
|
+
params,
|
|
318
|
+
})
|
|
319
|
+
return data
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Fetches a single page of `delegation` operations at the given block level.
|
|
324
|
+
* Internal — used by `fetchBlockDelegations` which handles pagination.
|
|
325
|
+
* https://api.tzkt.io/#operation/Operations_GetDelegations
|
|
326
|
+
*/
|
|
327
|
+
async getBlockDelegationsPage(level: number, cursor?: number): Promise<APIDelegationType[]> {
|
|
328
|
+
const params: Record<string, unknown> = { level, limit: BLOCK_PAGE_SIZE, 'sort.asc': 'id' }
|
|
329
|
+
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
330
|
+
const { data } = await network<APIDelegationType[]>({
|
|
331
|
+
url: `${explorerUrl}/v1/operations/delegations`,
|
|
332
|
+
params,
|
|
333
|
+
})
|
|
334
|
+
return data
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Fetches a single page of `staking` operations at the given block level.
|
|
339
|
+
* Internal — used by `fetchBlockStaking` which handles pagination.
|
|
340
|
+
* https://api.tzkt.io/#operation/Operations_GetStaking
|
|
341
|
+
*/
|
|
342
|
+
async getBlockStakingPage(level: number, cursor?: number): Promise<APIStakingType[]> {
|
|
343
|
+
const params: Record<string, unknown> = { level, limit: BLOCK_PAGE_SIZE, 'sort.asc': 'id' }
|
|
344
|
+
if (cursor !== undefined) params['offset.cr'] = cursor
|
|
345
|
+
const { data } = await network<APIStakingType[]>({
|
|
346
|
+
url: `${explorerUrl}/v1/operations/staking`,
|
|
347
|
+
params,
|
|
348
|
+
})
|
|
349
|
+
return data
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Fetches FA2 token transfers for a given account.
|
|
354
|
+
* Translates `query.sort` to TzKT's `sort.asc=id` / `sort.desc=id`.
|
|
355
|
+
* The lower-level `getTokenTransfers` helper is a generic pass-through and does not pin the sort.
|
|
356
|
+
* https://api.tzkt.io/#operation/Tokens_GetTokenTransfers
|
|
357
|
+
*/
|
|
358
|
+
async getAccountTokenTransfers(
|
|
359
|
+
address: string,
|
|
360
|
+
query: TokenTransfersGetOptions
|
|
361
|
+
): Promise<(APITokenTransfer & { hash: string; block: string })[]> {
|
|
362
|
+
const sortKey = query.sort === 'Descending' ? 'sort.desc' : 'sort.asc'
|
|
363
|
+
const params: Record<string, unknown> = {
|
|
364
|
+
'anyof.from.to': address,
|
|
365
|
+
'token.standard': 'fa2',
|
|
366
|
+
[sortKey]: 'id',
|
|
367
|
+
limit: query.limit,
|
|
368
|
+
'level.ge': query['level.ge'],
|
|
369
|
+
'level.lt': query['level.lt'],
|
|
370
|
+
'level.gt': query['level.gt'],
|
|
371
|
+
'id.lt': query['id.lt'],
|
|
372
|
+
'id.gt': query['id.gt'],
|
|
373
|
+
}
|
|
368
374
|
|
|
369
|
-
|
|
375
|
+
const data = await api.getTokenTransfers(clearUndefined(params))
|
|
370
376
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
377
|
+
const transactionIds = data
|
|
378
|
+
.map((t) => t.transactionId)
|
|
379
|
+
.filter((id): id is number => typeof id === 'number')
|
|
374
380
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
381
|
+
const originationIds = data
|
|
382
|
+
.map((t) => t.originationId)
|
|
383
|
+
.filter((id): id is number => typeof id === 'number')
|
|
378
384
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
385
|
+
if (transactionIds.length === 0 && originationIds.length === 0) {
|
|
386
|
+
return []
|
|
387
|
+
}
|
|
382
388
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
389
|
+
const transactions = transactionIds.length
|
|
390
|
+
? (
|
|
391
|
+
await Promise.all(
|
|
392
|
+
chunk(transactionIds, ID_IN_CHUNK_SIZE).map((ids) =>
|
|
393
|
+
api.getOperationsTransactions(query['level.ge'] || 0, undefined, {
|
|
394
|
+
'id.in': ids.join(','),
|
|
395
|
+
})
|
|
396
|
+
)
|
|
390
397
|
)
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
398
|
+
).flat()
|
|
399
|
+
: []
|
|
400
|
+
|
|
401
|
+
const originations = originationIds.length
|
|
402
|
+
? (
|
|
403
|
+
await Promise.all(
|
|
404
|
+
chunk(originationIds, ID_IN_CHUNK_SIZE).map((ids) =>
|
|
405
|
+
api.getOperationsOrigination(query['level.ge'] || 0, undefined, {
|
|
406
|
+
'id.in': ids.join(','),
|
|
407
|
+
})
|
|
408
|
+
)
|
|
402
409
|
)
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
410
|
+
).flat()
|
|
411
|
+
: []
|
|
412
|
+
|
|
413
|
+
// Build id -> operation maps once so per-transfer lookups are O(1) instead of O(n).
|
|
414
|
+
// Keys are widened to `number | undefined` so lookups with a missing id naturally
|
|
415
|
+
// return `undefined` (no entry is ever stored under the `undefined` key).
|
|
416
|
+
const transactionsById = new Map<number | undefined, (typeof transactions)[number]>(
|
|
417
|
+
transactions.map((t) => [t.id, t])
|
|
418
|
+
)
|
|
419
|
+
const originationsById = new Map<number | undefined, (typeof originations)[number]>(
|
|
420
|
+
originations.map((o) => [o.id, o])
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
return data.map((token) => {
|
|
424
|
+
const transaction = transactionsById.get(token.transactionId)
|
|
425
|
+
const origination = originationsById.get(token.originationId)
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
...token,
|
|
429
|
+
hash: transaction?.hash ?? origination?.hash ?? '',
|
|
430
|
+
block: transaction?.block ?? origination?.block ?? '',
|
|
431
|
+
}
|
|
432
|
+
})
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Fetches FA2 token balances for a given account.
|
|
437
|
+
* When `tokenFilter` is omitted, all FA2 token balances are returned.
|
|
438
|
+
* Pass `tokenFilter` to query a specific FA2 contract + token id (e.g. send-max for FA2).
|
|
439
|
+
* https://api.tzkt.io/#operation/Tokens_GetTokenBalances
|
|
440
|
+
*/
|
|
441
|
+
async getTokensBalances(
|
|
442
|
+
address: string,
|
|
443
|
+
tokenFilter?: { contractAddress: string; tokenId: number }
|
|
444
|
+
): Promise<APITokenBalance[]> {
|
|
445
|
+
const params: Record<string, unknown> = {
|
|
446
|
+
account: address,
|
|
447
|
+
'token.standard': 'fa2',
|
|
425
448
|
}
|
|
426
|
-
|
|
427
|
-
|
|
449
|
+
if (tokenFilter) {
|
|
450
|
+
params['token.contract'] = tokenFilter.contractAddress
|
|
451
|
+
params['token.tokenId'] = String(tokenFilter.tokenId)
|
|
452
|
+
}
|
|
453
|
+
const { data } = await network<APITokenBalance[]>({
|
|
454
|
+
url: `${explorerUrl}/v1/tokens/balances`,
|
|
455
|
+
params,
|
|
456
|
+
})
|
|
457
|
+
return data
|
|
458
|
+
},
|
|
459
|
+
}
|
|
428
460
|
|
|
429
|
-
|
|
430
|
-
* Fetches FA2 token balances for a given account.
|
|
431
|
-
* When `tokenFilter` is omitted, all FA2 token balances are returned.
|
|
432
|
-
* Pass `tokenFilter` to query a specific FA2 contract + token id (e.g. send-max for FA2).
|
|
433
|
-
* https://api.tzkt.io/#operation/Tokens_GetTokenBalances
|
|
434
|
-
*/
|
|
435
|
-
async getTokensBalances(
|
|
436
|
-
address: string,
|
|
437
|
-
tokenFilter?: { contractAddress: string; tokenId: number }
|
|
438
|
-
): Promise<APITokenBalance[]> {
|
|
439
|
-
const params: Record<string, unknown> = {
|
|
440
|
-
account: address,
|
|
441
|
-
'token.standard': 'fa2',
|
|
442
|
-
}
|
|
443
|
-
if (tokenFilter) {
|
|
444
|
-
params['token.contract'] = tokenFilter.contractAddress
|
|
445
|
-
params['token.tokenId'] = String(tokenFilter.tokenId)
|
|
446
|
-
}
|
|
447
|
-
const { data } = await network<APITokenBalance[]>({
|
|
448
|
-
url: `${getExplorerUrl()}/v1/tokens/balances`,
|
|
449
|
-
params,
|
|
450
|
-
})
|
|
451
|
-
return data
|
|
452
|
-
},
|
|
461
|
+
return api
|
|
453
462
|
}
|
|
454
463
|
|
|
464
|
+
/** The TzKT explorer client bound to a coin config, as returned by {@link createTzktApi}. */
|
|
465
|
+
export type TzktApi = ReturnType<typeof createTzktApi>
|
|
466
|
+
|
|
455
467
|
// TODO this has same purpose as api/listOperations
|
|
456
468
|
export const fetchAllTransactions = async (
|
|
469
|
+
config: TezosCoinConfig,
|
|
457
470
|
address: string,
|
|
458
471
|
lastId?: number
|
|
459
472
|
): Promise<APIOperation[]> => {
|
|
473
|
+
const api = createTzktApi(config)
|
|
460
474
|
let ops: APIOperation[] = []
|
|
461
|
-
let maxIteration =
|
|
475
|
+
let maxIteration = config.explorer.maxTxQuery
|
|
462
476
|
do {
|
|
463
477
|
const newOps = await api.getAccountOperations(address, {
|
|
464
478
|
lastId,
|
|
@@ -486,13 +500,14 @@ export const fetchAllTransactions = async (
|
|
|
486
500
|
* A safety cap (`maxTxQuery`) prevents infinite loops on pathological responses.
|
|
487
501
|
*/
|
|
488
502
|
async function fetchBlockPaginated<T extends { id: number }>(
|
|
503
|
+
config: TezosCoinConfig,
|
|
489
504
|
pageFn: (level: number, cursor?: number) => Promise<T[]>,
|
|
490
505
|
level: number,
|
|
491
506
|
label: string
|
|
492
507
|
): Promise<T[]> {
|
|
493
508
|
const items: T[] = []
|
|
494
509
|
let cursor: number | undefined
|
|
495
|
-
let maxIteration =
|
|
510
|
+
let maxIteration = config.explorer.maxTxQuery
|
|
496
511
|
do {
|
|
497
512
|
const page = await pageFn(level, cursor)
|
|
498
513
|
if (page.length === 0) break
|
|
@@ -506,22 +521,40 @@ async function fetchBlockPaginated<T extends { id: number }>(
|
|
|
506
521
|
return items
|
|
507
522
|
}
|
|
508
523
|
|
|
509
|
-
export const fetchBlockTransactions = (level: number) =>
|
|
510
|
-
fetchBlockPaginated(
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
524
|
+
export const fetchBlockTransactions = (config: TezosCoinConfig, level: number) =>
|
|
525
|
+
fetchBlockPaginated(
|
|
526
|
+
config,
|
|
527
|
+
createTzktApi(config).getBlockTransactionsPage,
|
|
528
|
+
level,
|
|
529
|
+
'fetchBlockTransactions'
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
export const fetchBlockTokenTransfers = (config: TezosCoinConfig, level: number) =>
|
|
533
|
+
fetchBlockPaginated(
|
|
534
|
+
config,
|
|
535
|
+
createTzktApi(config).getBlockTokenTransfersPage,
|
|
536
|
+
level,
|
|
537
|
+
'fetchBlockTokenTransfers'
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
export const fetchBlockDelegations = (config: TezosCoinConfig, level: number) =>
|
|
541
|
+
fetchBlockPaginated(
|
|
542
|
+
config,
|
|
543
|
+
createTzktApi(config).getBlockDelegationsPage,
|
|
544
|
+
level,
|
|
545
|
+
'fetchBlockDelegations'
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
export const fetchBlockStaking = (config: TezosCoinConfig, level: number) =>
|
|
549
|
+
fetchBlockPaginated(config, createTzktApi(config).getBlockStakingPage, level, 'fetchBlockStaking')
|
|
550
|
+
|
|
551
|
+
export const fetchBlockOriginations = (config: TezosCoinConfig, level: number) =>
|
|
552
|
+
fetchBlockPaginated(
|
|
553
|
+
config,
|
|
554
|
+
createTzktApi(config).getBlockOriginationsPage,
|
|
555
|
+
level,
|
|
556
|
+
'fetchBlockOriginations'
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
export const fetchBlockReveals = (config: TezosCoinConfig, level: number) =>
|
|
560
|
+
fetchBlockPaginated(config, createTzktApi(config).getBlockRevealsPage, level, 'fetchBlockReveals')
|