@fleet-sdk/blockchain-providers 0.6.0 → 0.6.1
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 +11 -0
- package/dist/index.d.mts +36 -8
- package/dist/index.d.ts +36 -8
- package/dist/index.js +159 -95
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +155 -97
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/ergo-graphql/ergoGraphQLProvider.ts +122 -123
- package/src/index.ts +3 -0
- package/src/utils/graphql.ts +1 -5
- package/src/utils/networking.ts +0 -1
@@ -12,18 +12,20 @@ import type {
|
|
12
12
|
import {
|
13
13
|
type Base58String,
|
14
14
|
type BlockHeader,
|
15
|
-
ensureDefaults,
|
16
15
|
type HexString,
|
16
|
+
type SignedTransaction,
|
17
|
+
ensureDefaults,
|
17
18
|
isEmpty,
|
18
19
|
isUndefined,
|
19
20
|
NotSupportedError,
|
20
21
|
orderBy,
|
21
|
-
type SignedTransaction,
|
22
22
|
some,
|
23
23
|
uniq,
|
24
|
-
uniqBy
|
24
|
+
uniqBy,
|
25
|
+
chunk
|
25
26
|
} from "@fleet-sdk/common";
|
26
27
|
import { ErgoAddress } from "@fleet-sdk/core";
|
28
|
+
import { hex } from "@fleet-sdk/crypto";
|
27
29
|
import type {
|
28
30
|
BoxQuery,
|
29
31
|
BoxWhere,
|
@@ -39,12 +41,11 @@ import type {
|
|
39
41
|
UnconfirmedTransactionWhere
|
40
42
|
} from "../types/blockchainProvider";
|
41
43
|
import {
|
42
|
-
createGqlOperation,
|
43
44
|
type GraphQLOperation,
|
44
45
|
type GraphQLRequestOptions,
|
45
46
|
type GraphQLSuccessResponse,
|
46
47
|
type GraphQLVariables,
|
47
|
-
|
48
|
+
createGqlOperation
|
48
49
|
} from "../utils";
|
49
50
|
import {
|
50
51
|
ALL_BOXES_QUERY,
|
@@ -57,14 +58,7 @@ import {
|
|
57
58
|
UNCONF_TX_QUERY
|
58
59
|
} from "./queries";
|
59
60
|
|
60
|
-
type GraphQLThrowableOptions = GraphQLRequestOptions & { throwOnNonNetworkErrors: true };
|
61
|
-
type OP<R, V extends GraphQLVariables> = GraphQLOperation<GraphQLSuccessResponse<R>, V>;
|
62
|
-
type BiMapper<T> = (value: string) => T;
|
63
|
-
|
64
61
|
export type GraphQLBoxWhere = BoxWhere & {
|
65
|
-
/** Base16-encoded BoxIds */
|
66
|
-
boxIds?: HexString[];
|
67
|
-
|
68
62
|
/** Base16-encoded ErgoTrees */
|
69
63
|
ergoTrees?: HexString[];
|
70
64
|
|
@@ -73,13 +67,11 @@ export type GraphQLBoxWhere = BoxWhere & {
|
|
73
67
|
};
|
74
68
|
|
75
69
|
export type GraphQLConfirmedTransactionWhere = ConfirmedTransactionWhere & {
|
76
|
-
transactionIds?: HexString[];
|
77
70
|
addresses?: (Base58String | ErgoAddress)[];
|
78
71
|
ergoTrees?: HexString[];
|
79
72
|
};
|
80
73
|
|
81
74
|
export type GraphQLUnconfirmedTransactionWhere = UnconfirmedTransactionWhere & {
|
82
|
-
transactionIds?: HexString[];
|
83
75
|
addresses?: (Base58String | ErgoAddress)[];
|
84
76
|
ergoTrees?: HexString[];
|
85
77
|
};
|
@@ -87,7 +79,7 @@ export type GraphQLUnconfirmedTransactionWhere = UnconfirmedTransactionWhere & {
|
|
87
79
|
export type GraphQLBoxQuery = BoxQuery<GraphQLBoxWhere>;
|
88
80
|
export type ErgoGraphQLRequestOptions = Omit<
|
89
81
|
GraphQLRequestOptions,
|
90
|
-
"
|
82
|
+
"throwOnNonNetworkErrors"
|
91
83
|
>;
|
92
84
|
|
93
85
|
type ConfirmedBoxesResponse = { boxes: GQLBox[] };
|
@@ -100,7 +92,15 @@ type CheckTransactionResponse = { checkTransaction: string };
|
|
100
92
|
type TransactionSubmissionResponse = { submitTransaction: string };
|
101
93
|
type SignedTxArgsResp = { signedTransaction: SignedTransaction };
|
102
94
|
|
95
|
+
type GraphQLThrowableOptions = ErgoGraphQLRequestOptions & {
|
96
|
+
throwOnNonNetworkErrors: true;
|
97
|
+
};
|
98
|
+
|
99
|
+
type OP<R, V extends GraphQLVariables> = GraphQLOperation<GraphQLSuccessResponse<R>, V>;
|
100
|
+
type BiMapper<T> = (value: string) => T;
|
101
|
+
|
103
102
|
const PAGE_SIZE = 50;
|
103
|
+
const MAX_ARGS = 20;
|
104
104
|
|
105
105
|
export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
106
106
|
#options: GraphQLThrowableOptions;
|
@@ -116,15 +116,14 @@ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
|
116
116
|
#getHeaders!: OP<BlockHeadersResponse, QueryBlockHeadersArgs>;
|
117
117
|
|
118
118
|
constructor(url: string);
|
119
|
-
constructor(
|
119
|
+
constructor(options: ErgoGraphQLRequestOptions);
|
120
120
|
constructor(optOrUrl: ErgoGraphQLRequestOptions | string) {
|
121
|
+
this.#biMapper = (value) => BigInt(value) as I;
|
121
122
|
this.#options = {
|
122
123
|
...(isRequestParam(optOrUrl) ? optOrUrl : { url: optOrUrl }),
|
123
124
|
throwOnNonNetworkErrors: true
|
124
125
|
};
|
125
126
|
|
126
|
-
this.#biMapper = (value) => BigInt(value) as I;
|
127
|
-
|
128
127
|
this.#getConfirmedBoxes = this.createOperation(CONF_BOXES_QUERY);
|
129
128
|
this.#getUnconfirmedBoxes = this.createOperation(UNCONF_BOXES_QUERY);
|
130
129
|
this.#getAllBoxes = this.createOperation(ALL_BOXES_QUERY);
|
@@ -137,10 +136,10 @@ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
|
137
136
|
|
138
137
|
#fetchBoxes(args: QueryBoxesArgs, inclConf: boolean, inclUnconf: boolean) {
|
139
138
|
return inclConf && inclUnconf
|
140
|
-
? this.#getAllBoxes(args
|
139
|
+
? this.#getAllBoxes(args)
|
141
140
|
: inclUnconf
|
142
|
-
? this.#getUnconfirmedBoxes(args
|
143
|
-
: this.#getConfirmedBoxes(args
|
141
|
+
? this.#getUnconfirmedBoxes(args)
|
142
|
+
: this.#getConfirmedBoxes(args);
|
144
143
|
}
|
145
144
|
|
146
145
|
setUrl(url: string): ErgoGraphQLProvider<I> {
|
@@ -161,55 +160,57 @@ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
|
161
160
|
const notBeingSpent = (box: GQLBox) => !box.beingSpent;
|
162
161
|
const returnedBoxIds = new Set<string>();
|
163
162
|
const { where, from } = query;
|
164
|
-
const
|
163
|
+
const queries = buildGqlBoxQueries(where);
|
164
|
+
const isMempoolAware = from !== "blockchain";
|
165
165
|
|
166
|
-
|
167
|
-
|
168
|
-
|
166
|
+
for (const query of queries) {
|
167
|
+
let inclChain = from !== "mempool";
|
168
|
+
let inclPool = from !== "blockchain";
|
169
169
|
|
170
|
-
|
171
|
-
|
172
|
-
|
170
|
+
while (inclChain || inclPool) {
|
171
|
+
const { data } = await this.#fetchBoxes(query, inclChain, inclPool);
|
172
|
+
let boxes: ChainProviderBox<I>[] = [];
|
173
173
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
174
|
+
if (inclChain && hasConfirmed(data)) {
|
175
|
+
if (some(data.boxes)) {
|
176
|
+
const confirmedBoxes = (
|
177
|
+
isMempoolAware ? data.boxes.filter(notBeingSpent) : data.boxes
|
178
|
+
).map((b) => mapConfirmedBox(b, this.#biMapper));
|
179
179
|
|
180
|
-
|
181
|
-
|
180
|
+
boxes = boxes.concat(confirmedBoxes);
|
181
|
+
}
|
182
182
|
|
183
|
-
|
184
|
-
}
|
185
|
-
|
186
|
-
if (isMempoolAware && hasMempool(data)) {
|
187
|
-
if (some(data.mempool.boxes)) {
|
188
|
-
const mempoolBoxes = data.mempool.boxes
|
189
|
-
.filter(notBeingSpent)
|
190
|
-
.map((b) => mapUnconfirmedBox(b, this.#biMapper));
|
191
|
-
boxes = boxes.concat(mempoolBoxes);
|
183
|
+
inclChain = data.boxes.length === PAGE_SIZE;
|
192
184
|
}
|
193
185
|
|
194
|
-
|
195
|
-
|
186
|
+
if (isMempoolAware && hasMempool(data)) {
|
187
|
+
if (some(data.mempool.boxes)) {
|
188
|
+
const mempoolBoxes = data.mempool.boxes
|
189
|
+
.filter(notBeingSpent)
|
190
|
+
.map((b) => mapUnconfirmedBox(b, this.#biMapper));
|
191
|
+
boxes = boxes.concat(mempoolBoxes);
|
192
|
+
}
|
196
193
|
|
197
|
-
|
198
|
-
// boxes can be moved from the mempool to the blockchain while streaming,
|
199
|
-
// so we need to filter out boxes that have already been returned.
|
200
|
-
if (boxes.some((box) => returnedBoxIds.has(box.boxId))) {
|
201
|
-
boxes = boxes.filter((b) => !returnedBoxIds.has(b.boxId));
|
194
|
+
inclPool = data.mempool.boxes.length === PAGE_SIZE;
|
202
195
|
}
|
203
196
|
|
204
197
|
if (some(boxes)) {
|
205
|
-
boxes
|
206
|
-
|
207
|
-
|
198
|
+
// boxes can be moved from the mempool to the blockchain while streaming,
|
199
|
+
// so we need to filter out boxes that have already been returned.
|
200
|
+
if (boxes.some((box) => returnedBoxIds.has(box.boxId))) {
|
201
|
+
boxes = boxes.filter((b) => !returnedBoxIds.has(b.boxId));
|
202
|
+
}
|
203
|
+
|
204
|
+
if (some(boxes)) {
|
205
|
+
boxes = uniqBy(boxes, (box) => box.boxId);
|
206
|
+
for (const box of boxes) returnedBoxIds.add(box.boxId);
|
207
|
+
yield boxes;
|
208
|
+
}
|
208
209
|
}
|
209
|
-
}
|
210
210
|
|
211
|
-
|
212
|
-
|
211
|
+
if (inclChain || inclPool) query.skip += PAGE_SIZE;
|
212
|
+
}
|
213
|
+
}
|
213
214
|
}
|
214
215
|
|
215
216
|
async getBoxes(query: GraphQLBoxQuery): Promise<ChainProviderBox<I>[]> {
|
@@ -221,19 +222,21 @@ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
|
221
222
|
async *streamUnconfirmedTransactions(
|
222
223
|
query: TransactionQuery<GraphQLUnconfirmedTransactionWhere>
|
223
224
|
): AsyncIterable<ChainProviderUnconfirmedTransaction<I>[]> {
|
224
|
-
const
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
225
|
+
const queries = buildGqlUnconfirmedTxQueries(query.where);
|
226
|
+
|
227
|
+
for (const query of queries) {
|
228
|
+
let keepFetching = true;
|
229
|
+
while (keepFetching) {
|
230
|
+
const response = await this.#getUnconfirmedTransactions(query);
|
231
|
+
if (some(response.data?.mempool?.transactions)) {
|
232
|
+
yield response.data.mempool.transactions.map((t) =>
|
233
|
+
mapUnconfirmedTransaction(t, this.#biMapper)
|
234
|
+
);
|
235
|
+
}
|
234
236
|
|
235
|
-
|
236
|
-
|
237
|
+
keepFetching = response.data?.mempool?.transactions?.length === PAGE_SIZE;
|
238
|
+
if (keepFetching) query.skip += PAGE_SIZE;
|
239
|
+
}
|
237
240
|
}
|
238
241
|
}
|
239
242
|
|
@@ -251,19 +254,21 @@ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
|
251
254
|
async *streamConfirmedTransactions(
|
252
255
|
query: TransactionQuery<GraphQLConfirmedTransactionWhere>
|
253
256
|
): AsyncIterable<ChainProviderConfirmedTransaction<I>[]> {
|
254
|
-
const
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
257
|
+
const queries = buildGqlConfirmedTxQueries(query.where);
|
258
|
+
|
259
|
+
for (const query of queries) {
|
260
|
+
let keepFetching = true;
|
261
|
+
while (keepFetching) {
|
262
|
+
const response = await this.#getConfirmedTransactions(query);
|
263
|
+
if (some(response.data?.transactions)) {
|
264
|
+
yield response.data.transactions.map((t) =>
|
265
|
+
mapConfirmedTransaction(t, this.#biMapper)
|
266
|
+
);
|
267
|
+
}
|
264
268
|
|
265
|
-
|
266
|
-
|
269
|
+
keepFetching = response.data?.transactions?.length === PAGE_SIZE;
|
270
|
+
if (keepFetching) query.skip += PAGE_SIZE;
|
271
|
+
}
|
267
272
|
}
|
268
273
|
}
|
269
274
|
|
@@ -279,15 +284,15 @@ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
|
279
284
|
}
|
280
285
|
|
281
286
|
async getHeaders(query: HeaderQuery): Promise<BlockHeader[]> {
|
282
|
-
const response = await this.#getHeaders(query
|
287
|
+
const response = await this.#getHeaders(query);
|
283
288
|
|
284
289
|
return (
|
285
|
-
response.data?.blockHeaders.map((
|
286
|
-
...
|
287
|
-
id:
|
288
|
-
timestamp: Number(
|
289
|
-
nBits: Number(
|
290
|
-
votes:
|
290
|
+
response.data?.blockHeaders.map((h) => ({
|
291
|
+
...h,
|
292
|
+
id: h.headerId,
|
293
|
+
timestamp: Number(h.timestamp),
|
294
|
+
nBits: Number(h.nBits),
|
295
|
+
votes: hex.encode(Uint8Array.from(h.votes))
|
291
296
|
})) ?? []
|
292
297
|
);
|
293
298
|
}
|
@@ -306,10 +311,7 @@ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
|
306
311
|
signedTransaction: SignedTransaction
|
307
312
|
): Promise<TransactionEvaluationResult> {
|
308
313
|
try {
|
309
|
-
const response = await this.#checkTransaction(
|
310
|
-
{ signedTransaction },
|
311
|
-
this.#options.url
|
312
|
-
);
|
314
|
+
const response = await this.#checkTransaction({ signedTransaction });
|
313
315
|
return { success: true, transactionId: response.data.checkTransaction };
|
314
316
|
} catch (e) {
|
315
317
|
return { success: false, message: (e as Error).message };
|
@@ -320,10 +322,7 @@ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
|
320
322
|
signedTransaction: SignedTransaction
|
321
323
|
): Promise<TransactionEvaluationResult> {
|
322
324
|
try {
|
323
|
-
const response = await this.#sendTransaction(
|
324
|
-
{ signedTransaction },
|
325
|
-
this.#options.url
|
326
|
-
);
|
325
|
+
const response = await this.#sendTransaction({ signedTransaction });
|
327
326
|
return { success: true, transactionId: response.data.submitTransaction };
|
328
327
|
} catch (e) {
|
329
328
|
return { success: false, message: (e as Error).message };
|
@@ -335,32 +334,28 @@ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
|
335
334
|
}
|
336
335
|
}
|
337
336
|
|
338
|
-
function
|
339
|
-
const
|
337
|
+
function buildGqlBoxQueries(where: GraphQLBoxWhere) {
|
338
|
+
const ergoTrees = uniq(
|
339
|
+
[
|
340
|
+
merge(where.ergoTrees, where.ergoTree) ?? [],
|
341
|
+
merge(where.addresses, where.address)?.map((a) =>
|
342
|
+
typeof a === "string" ? ErgoAddress.decode(a).ergoTree : a.ergoTree
|
343
|
+
) ?? []
|
344
|
+
].flat()
|
345
|
+
);
|
346
|
+
|
347
|
+
return chunk(ergoTrees, MAX_ARGS).map((chunk) => ({
|
340
348
|
spent: false,
|
341
|
-
boxIds:
|
342
|
-
ergoTrees:
|
349
|
+
boxIds: where.boxId ? [where.boxId] : undefined,
|
350
|
+
ergoTrees: chunk,
|
343
351
|
ergoTreeTemplateHash: where.templateHash,
|
344
352
|
tokenId: where.tokenId,
|
345
353
|
skip: 0,
|
346
354
|
take: PAGE_SIZE
|
347
|
-
}
|
348
|
-
|
349
|
-
const addresses = merge(where.addresses, where.address);
|
350
|
-
if (some(addresses)) {
|
351
|
-
const trees = addresses.map((address) =>
|
352
|
-
typeof address === "string"
|
353
|
-
? ErgoAddress.decode(address).ergoTree
|
354
|
-
: address.ergoTree
|
355
|
-
);
|
356
|
-
|
357
|
-
args.ergoTrees = uniq(some(args.ergoTrees) ? args.ergoTrees.concat(trees) : trees);
|
358
|
-
}
|
359
|
-
|
360
|
-
return args;
|
355
|
+
}));
|
361
356
|
}
|
362
357
|
|
363
|
-
function
|
358
|
+
function buildGqlUnconfirmedTxQueries(where: GraphQLConfirmedTransactionWhere) {
|
364
359
|
const addresses = uniq(
|
365
360
|
[
|
366
361
|
merge(where.addresses, where.address)?.map((address): string =>
|
@@ -372,21 +367,21 @@ function buildGqlUnconfirmedTxQueryArgs(where: GraphQLConfirmedTransactionWhere)
|
|
372
367
|
].flat()
|
373
368
|
);
|
374
369
|
|
375
|
-
return {
|
376
|
-
addresses:
|
377
|
-
transactionIds:
|
370
|
+
return chunk(addresses, MAX_ARGS).map((chunk) => ({
|
371
|
+
addresses: chunk.length ? chunk : undefined,
|
372
|
+
transactionIds: where.transactionId ? [where.transactionId] : undefined,
|
378
373
|
skip: 0,
|
379
374
|
take: PAGE_SIZE
|
380
|
-
};
|
375
|
+
}));
|
381
376
|
}
|
382
377
|
|
383
|
-
function
|
384
|
-
return {
|
385
|
-
...
|
378
|
+
function buildGqlConfirmedTxQueries(where: GraphQLConfirmedTransactionWhere) {
|
379
|
+
return buildGqlUnconfirmedTxQueries(where).map((query) => ({
|
380
|
+
...query,
|
386
381
|
headerId: where.headerId,
|
387
382
|
minHeight: where.minHeight,
|
388
383
|
onlyRelevantOutputs: where.onlyRelevantOutputs
|
389
|
-
};
|
384
|
+
}));
|
390
385
|
}
|
391
386
|
|
392
387
|
function merge<T>(array?: T[], el?: T) {
|
@@ -481,3 +476,7 @@ function mapConfirmedTransaction<T>(
|
|
481
476
|
confirmed: true
|
482
477
|
};
|
483
478
|
}
|
479
|
+
|
480
|
+
export function isRequestParam(obj: unknown): obj is ErgoGraphQLRequestOptions {
|
481
|
+
return typeof obj === "object" && (obj as ErgoGraphQLRequestOptions).url !== undefined;
|
482
|
+
}
|
package/src/index.ts
CHANGED
package/src/utils/graphql.ts
CHANGED
@@ -9,7 +9,7 @@ import type { FallbackRetryOptions, ParserLike } from "./networking";
|
|
9
9
|
import { request } from "./networking";
|
10
10
|
|
11
11
|
const OP_NAME_REGEX = /(query|mutation)\s?([\w\-_]+)?/;
|
12
|
-
|
12
|
+
const DEFAULT_HEADERS = {
|
13
13
|
"content-type": "application/json; charset=utf-8",
|
14
14
|
accept: "application/graphql-response+json, application/json"
|
15
15
|
};
|
@@ -118,7 +118,3 @@ export function gql(query: TemplateStringsArray): string {
|
|
118
118
|
export function getOpName(query: string): string | undefined {
|
119
119
|
return OP_NAME_REGEX.exec(query)?.at(2);
|
120
120
|
}
|
121
|
-
|
122
|
-
export function isRequestParam(obj: unknown): obj is GraphQLRequestOptions {
|
123
|
-
return typeof obj === "object" && (obj as GraphQLRequestOptions).url !== undefined;
|
124
|
-
}
|