@fleet-sdk/blockchain-providers 0.5.0 → 0.6.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 +12 -0
- package/dist/index.d.mts +124 -33
- package/dist/index.d.ts +124 -33
- package/dist/index.js +241 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +241 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/ergo-graphql/ergoGraphQLProvider.ts +256 -86
- package/src/ergo-graphql/queries.ts +14 -5
- package/src/types/blockchainProvider.ts +101 -18
- package/src/utils/_tests.ts +6 -10
- package/src/utils/graphql.ts +54 -62
- package/src/utils/networking.ts +84 -0
@@ -1,8 +1,13 @@
|
|
1
1
|
import type {
|
2
|
-
Box,
|
2
|
+
Box as GQLBox,
|
3
3
|
QueryBoxesArgs,
|
4
4
|
Header,
|
5
|
-
QueryBlockHeadersArgs
|
5
|
+
QueryBlockHeadersArgs,
|
6
|
+
Transaction,
|
7
|
+
QueryTransactionsArgs,
|
8
|
+
MempoolTransactionsArgs,
|
9
|
+
UnconfirmedTransaction,
|
10
|
+
UnconfirmedBox as GQLUnconfirmedBox
|
6
11
|
} from "@ergo-graphql/types";
|
7
12
|
import {
|
8
13
|
type Base58String,
|
@@ -23,17 +28,21 @@ import type {
|
|
23
28
|
BoxQuery,
|
24
29
|
BoxWhere,
|
25
30
|
ChainProviderBox,
|
31
|
+
ChainProviderConfirmedTransaction,
|
32
|
+
ChainProviderUnconfirmedTransaction,
|
26
33
|
HeaderQuery,
|
27
34
|
IBlockchainProvider,
|
28
35
|
TransactionEvaluationResult,
|
29
|
-
|
36
|
+
TransactionQuery,
|
37
|
+
TransactionReductionResult,
|
38
|
+
ConfirmedTransactionWhere,
|
39
|
+
UnconfirmedTransactionWhere
|
30
40
|
} from "../types/blockchainProvider";
|
31
41
|
import {
|
32
42
|
createGqlOperation,
|
33
43
|
type GraphQLOperation,
|
34
44
|
type GraphQLRequestOptions,
|
35
45
|
type GraphQLSuccessResponse,
|
36
|
-
type GraphQLThrowableOptions,
|
37
46
|
type GraphQLVariables,
|
38
47
|
isRequestParam
|
39
48
|
} from "../utils";
|
@@ -41,11 +50,17 @@ import {
|
|
41
50
|
ALL_BOXES_QUERY,
|
42
51
|
CHECK_TX_MUTATION,
|
43
52
|
CONF_BOXES_QUERY,
|
53
|
+
CONF_TX_QUERY,
|
44
54
|
HEADERS_QUERY,
|
45
55
|
SEND_TX_MUTATION,
|
46
|
-
UNCONF_BOXES_QUERY
|
56
|
+
UNCONF_BOXES_QUERY,
|
57
|
+
UNCONF_TX_QUERY
|
47
58
|
} from "./queries";
|
48
59
|
|
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
|
+
|
49
64
|
export type GraphQLBoxWhere = BoxWhere & {
|
50
65
|
/** Base16-encoded BoxIds */
|
51
66
|
boxIds?: HexString[];
|
@@ -57,15 +72,29 @@ export type GraphQLBoxWhere = BoxWhere & {
|
|
57
72
|
addresses?: (Base58String | ErgoAddress)[];
|
58
73
|
};
|
59
74
|
|
75
|
+
export type GraphQLConfirmedTransactionWhere = ConfirmedTransactionWhere & {
|
76
|
+
transactionIds?: HexString[];
|
77
|
+
addresses?: (Base58String | ErgoAddress)[];
|
78
|
+
ergoTrees?: HexString[];
|
79
|
+
};
|
80
|
+
|
81
|
+
export type GraphQLUnconfirmedTransactionWhere = UnconfirmedTransactionWhere & {
|
82
|
+
transactionIds?: HexString[];
|
83
|
+
addresses?: (Base58String | ErgoAddress)[];
|
84
|
+
ergoTrees?: HexString[];
|
85
|
+
};
|
86
|
+
|
60
87
|
export type GraphQLBoxQuery = BoxQuery<GraphQLBoxWhere>;
|
61
88
|
export type ErgoGraphQLRequestOptions = Omit<
|
62
89
|
GraphQLRequestOptions,
|
63
90
|
"throwOnNonNetworkError"
|
64
91
|
>;
|
65
92
|
|
66
|
-
type ConfirmedBoxesResponse = { boxes:
|
67
|
-
type UnconfirmedBoxesResponse = { mempool: { boxes:
|
93
|
+
type ConfirmedBoxesResponse = { boxes: GQLBox[] };
|
94
|
+
type UnconfirmedBoxesResponse = { mempool: { boxes: GQLBox[] } };
|
68
95
|
type CombinedBoxesResponse = ConfirmedBoxesResponse & UnconfirmedBoxesResponse;
|
96
|
+
type UnconfirmedTxResponse = { mempool: { transactions: UnconfirmedTransaction[] } };
|
97
|
+
type ConfirmedTxResponse = { transactions: Transaction[] };
|
69
98
|
type BlockHeadersResponse = { blockHeaders: Header[] };
|
70
99
|
type CheckTransactionResponse = { checkTransaction: string };
|
71
100
|
type TransactionSubmissionResponse = { submitTransaction: string };
|
@@ -73,71 +102,63 @@ type SignedTxArgsResp = { signedTransaction: SignedTransaction };
|
|
73
102
|
|
74
103
|
const PAGE_SIZE = 50;
|
75
104
|
|
76
|
-
export class ErgoGraphQLProvider implements IBlockchainProvider<
|
105
|
+
export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
|
77
106
|
#options: GraphQLThrowableOptions;
|
78
|
-
|
79
|
-
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
|
86
|
-
|
107
|
+
#biMapper: BiMapper<I>;
|
108
|
+
|
109
|
+
#getConfirmedBoxes: OP<ConfirmedBoxesResponse, QueryBoxesArgs>;
|
110
|
+
#getUnconfirmedBoxes: OP<UnconfirmedBoxesResponse, QueryBoxesArgs>;
|
111
|
+
#getAllBoxes: OP<CombinedBoxesResponse, QueryBoxesArgs>;
|
112
|
+
#getConfirmedTransactions: OP<ConfirmedTxResponse, QueryTransactionsArgs>;
|
113
|
+
#getUnconfirmedTransactions: OP<UnconfirmedTxResponse, MempoolTransactionsArgs>;
|
114
|
+
#checkTransaction: OP<CheckTransactionResponse, SignedTxArgsResp>;
|
115
|
+
#sendTransaction: OP<TransactionSubmissionResponse, SignedTxArgsResp>;
|
116
|
+
#getHeaders!: OP<BlockHeadersResponse, QueryBlockHeadersArgs>;
|
117
|
+
|
118
|
+
constructor(url: string);
|
87
119
|
constructor(url: ErgoGraphQLRequestOptions);
|
88
|
-
constructor(optOrUrl: ErgoGraphQLRequestOptions | string
|
120
|
+
constructor(optOrUrl: ErgoGraphQLRequestOptions | string) {
|
89
121
|
this.#options = {
|
90
122
|
...(isRequestParam(optOrUrl) ? optOrUrl : { url: optOrUrl }),
|
91
123
|
throwOnNonNetworkErrors: true
|
92
124
|
};
|
93
125
|
|
94
|
-
this.#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
this.#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
this.#getAllBoxes = this.createOperation<
|
105
|
-
CombinedBoxesResponse,
|
106
|
-
QueryBoxesArgs
|
107
|
-
>(ALL_BOXES_QUERY);
|
108
|
-
|
109
|
-
this.#getHeaders = this.createOperation<
|
110
|
-
BlockHeadersResponse,
|
111
|
-
QueryBlockHeadersArgs
|
112
|
-
>(HEADERS_QUERY);
|
113
|
-
|
114
|
-
this.#checkTx = this.createOperation<
|
115
|
-
CheckTransactionResponse,
|
116
|
-
SignedTxArgsResp
|
117
|
-
>(CHECK_TX_MUTATION);
|
118
|
-
|
119
|
-
this.#sendTx = this.createOperation<
|
120
|
-
TransactionSubmissionResponse,
|
121
|
-
SignedTxArgsResp
|
122
|
-
>(SEND_TX_MUTATION);
|
126
|
+
this.#biMapper = (value) => BigInt(value) as I;
|
127
|
+
|
128
|
+
this.#getConfirmedBoxes = this.createOperation(CONF_BOXES_QUERY);
|
129
|
+
this.#getUnconfirmedBoxes = this.createOperation(UNCONF_BOXES_QUERY);
|
130
|
+
this.#getAllBoxes = this.createOperation(ALL_BOXES_QUERY);
|
131
|
+
this.#getConfirmedTransactions = this.createOperation(CONF_TX_QUERY);
|
132
|
+
this.#getUnconfirmedTransactions = this.createOperation(UNCONF_TX_QUERY);
|
133
|
+
this.#checkTransaction = this.createOperation(CHECK_TX_MUTATION);
|
134
|
+
this.#sendTransaction = this.createOperation(SEND_TX_MUTATION);
|
135
|
+
this.#getHeaders = this.createOperation(HEADERS_QUERY);
|
123
136
|
}
|
124
137
|
|
125
138
|
#fetchBoxes(args: QueryBoxesArgs, inclConf: boolean, inclUnconf: boolean) {
|
126
139
|
return inclConf && inclUnconf
|
127
|
-
? this.#getAllBoxes(args)
|
140
|
+
? this.#getAllBoxes(args, this.#options.url)
|
128
141
|
: inclUnconf
|
129
|
-
? this.#
|
130
|
-
: this.#
|
142
|
+
? this.#getUnconfirmedBoxes(args, this.#options.url)
|
143
|
+
: this.#getConfirmedBoxes(args, this.#options.url);
|
144
|
+
}
|
145
|
+
|
146
|
+
setUrl(url: string): ErgoGraphQLProvider<I> {
|
147
|
+
this.#options.url = url;
|
148
|
+
return this;
|
149
|
+
}
|
150
|
+
|
151
|
+
setBigIntMapper<M>(mapper: BiMapper<M>): ErgoGraphQLProvider<M> {
|
152
|
+
this.#biMapper = mapper as unknown as BiMapper<I>;
|
153
|
+
return this as unknown as ErgoGraphQLProvider<M>;
|
131
154
|
}
|
132
155
|
|
133
|
-
async *streamBoxes(
|
134
|
-
query: GraphQLBoxQuery
|
135
|
-
): AsyncGenerator<ChainProviderBox[]> {
|
156
|
+
async *streamBoxes(query: GraphQLBoxQuery): AsyncGenerator<ChainProviderBox<I>[]> {
|
136
157
|
if (isEmpty(query.where)) {
|
137
158
|
throw new Error("Cannot fetch unspent boxes without a where clause.");
|
138
159
|
}
|
139
160
|
|
140
|
-
const notBeingSpent = (box:
|
161
|
+
const notBeingSpent = (box: GQLBox) => !box.beingSpent;
|
141
162
|
const returnedBoxIds = new Set<string>();
|
142
163
|
const { where, from } = query;
|
143
164
|
const args = buildGqlBoxQueryArgs(where);
|
@@ -147,16 +168,14 @@ export class ErgoGraphQLProvider implements IBlockchainProvider<BoxWhere> {
|
|
147
168
|
const isMempoolAware = inclPool;
|
148
169
|
|
149
170
|
do {
|
150
|
-
const
|
151
|
-
|
152
|
-
const { data } = response;
|
153
|
-
let boxes: ChainProviderBox[] = [];
|
171
|
+
const { data } = await this.#fetchBoxes(args, inclChain, inclPool);
|
172
|
+
let boxes: ChainProviderBox<I>[] = [];
|
154
173
|
|
155
174
|
if (inclChain && hasConfirmed(data)) {
|
156
175
|
if (some(data.boxes)) {
|
157
176
|
const confirmedBoxes = (
|
158
177
|
isMempoolAware ? data.boxes.filter(notBeingSpent) : data.boxes
|
159
|
-
).map(
|
178
|
+
).map((b) => mapConfirmedBox(b, this.#biMapper));
|
160
179
|
|
161
180
|
boxes = boxes.concat(confirmedBoxes);
|
162
181
|
}
|
@@ -168,7 +187,7 @@ export class ErgoGraphQLProvider implements IBlockchainProvider<BoxWhere> {
|
|
168
187
|
if (some(data.mempool.boxes)) {
|
169
188
|
const mempoolBoxes = data.mempool.boxes
|
170
189
|
.filter(notBeingSpent)
|
171
|
-
.map(
|
190
|
+
.map((b) => mapUnconfirmedBox(b, this.#biMapper));
|
172
191
|
boxes = boxes.concat(mempoolBoxes);
|
173
192
|
}
|
174
193
|
|
@@ -193,17 +212,74 @@ export class ErgoGraphQLProvider implements IBlockchainProvider<BoxWhere> {
|
|
193
212
|
} while (inclChain || inclPool);
|
194
213
|
}
|
195
214
|
|
196
|
-
async getBoxes(query: GraphQLBoxQuery): Promise<ChainProviderBox[]> {
|
197
|
-
|
198
|
-
for await (const chunk of this.streamBoxes(query))
|
199
|
-
|
215
|
+
async getBoxes(query: GraphQLBoxQuery): Promise<ChainProviderBox<I>[]> {
|
216
|
+
const boxes: ChainProviderBox<I>[][] = [];
|
217
|
+
for await (const chunk of this.streamBoxes(query)) boxes.push(chunk);
|
218
|
+
return orderBy(boxes.flat(), (box) => box.creationHeight);
|
219
|
+
}
|
220
|
+
|
221
|
+
async *streamUnconfirmedTransactions(
|
222
|
+
query: TransactionQuery<GraphQLUnconfirmedTransactionWhere>
|
223
|
+
): AsyncIterable<ChainProviderUnconfirmedTransaction<I>[]> {
|
224
|
+
const args = buildGqlUnconfirmedTxQueryArgs(query.where);
|
225
|
+
|
226
|
+
let keepFetching = true;
|
227
|
+
while (keepFetching) {
|
228
|
+
const response = await this.#getUnconfirmedTransactions(args);
|
229
|
+
if (some(response.data?.mempool?.transactions)) {
|
230
|
+
yield response.data.mempool.transactions.map((t) =>
|
231
|
+
mapUnconfirmedTransaction(t, this.#biMapper)
|
232
|
+
);
|
233
|
+
}
|
234
|
+
|
235
|
+
keepFetching = response.data?.mempool?.transactions?.length === PAGE_SIZE;
|
236
|
+
if (keepFetching) args.skip += PAGE_SIZE;
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
async getUnconfirmedTransactions(
|
241
|
+
query: TransactionQuery<GraphQLUnconfirmedTransactionWhere>
|
242
|
+
): Promise<ChainProviderUnconfirmedTransaction<I>[]> {
|
243
|
+
const transactions: ChainProviderUnconfirmedTransaction<I>[][] = [];
|
244
|
+
for await (const chunk of this.streamUnconfirmedTransactions(query)) {
|
245
|
+
transactions.push(chunk);
|
246
|
+
}
|
247
|
+
|
248
|
+
return transactions.flat();
|
249
|
+
}
|
250
|
+
|
251
|
+
async *streamConfirmedTransactions(
|
252
|
+
query: TransactionQuery<GraphQLConfirmedTransactionWhere>
|
253
|
+
): AsyncIterable<ChainProviderConfirmedTransaction<I>[]> {
|
254
|
+
const args = buildGqlConfirmedTxQueryArgs(query.where);
|
255
|
+
|
256
|
+
let keepFetching = true;
|
257
|
+
while (keepFetching) {
|
258
|
+
const response = await this.#getConfirmedTransactions(args);
|
259
|
+
if (some(response.data?.transactions)) {
|
260
|
+
yield response.data.transactions.map((t) =>
|
261
|
+
mapConfirmedTransaction(t, this.#biMapper)
|
262
|
+
);
|
263
|
+
}
|
264
|
+
|
265
|
+
keepFetching = response.data?.transactions?.length === PAGE_SIZE;
|
266
|
+
if (keepFetching) args.skip += PAGE_SIZE;
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
async getConfirmedTransactions(
|
271
|
+
query: TransactionQuery<GraphQLConfirmedTransactionWhere>
|
272
|
+
): Promise<ChainProviderConfirmedTransaction<I>[]> {
|
273
|
+
const transactions: ChainProviderConfirmedTransaction<I>[][] = [];
|
274
|
+
for await (const chunk of this.streamConfirmedTransactions(query)) {
|
275
|
+
transactions.push(chunk);
|
200
276
|
}
|
201
277
|
|
202
|
-
return
|
278
|
+
return transactions.flat();
|
203
279
|
}
|
204
280
|
|
205
281
|
async getHeaders(query: HeaderQuery): Promise<BlockHeader[]> {
|
206
|
-
const response = await this.#getHeaders(query);
|
282
|
+
const response = await this.#getHeaders(query, this.#options.url);
|
207
283
|
|
208
284
|
return (
|
209
285
|
response.data?.blockHeaders.map((header) => ({
|
@@ -230,8 +306,10 @@ export class ErgoGraphQLProvider implements IBlockchainProvider<BoxWhere> {
|
|
230
306
|
signedTransaction: SignedTransaction
|
231
307
|
): Promise<TransactionEvaluationResult> {
|
232
308
|
try {
|
233
|
-
const response = await this.#
|
234
|
-
|
309
|
+
const response = await this.#checkTransaction(
|
310
|
+
{ signedTransaction },
|
311
|
+
this.#options.url
|
312
|
+
);
|
235
313
|
return { success: true, transactionId: response.data.checkTransaction };
|
236
314
|
} catch (e) {
|
237
315
|
return { success: false, message: (e as Error).message };
|
@@ -242,8 +320,10 @@ export class ErgoGraphQLProvider implements IBlockchainProvider<BoxWhere> {
|
|
242
320
|
signedTransaction: SignedTransaction
|
243
321
|
): Promise<TransactionEvaluationResult> {
|
244
322
|
try {
|
245
|
-
const response = await this.#
|
246
|
-
|
323
|
+
const response = await this.#sendTransaction(
|
324
|
+
{ signedTransaction },
|
325
|
+
this.#options.url
|
326
|
+
);
|
247
327
|
return { success: true, transactionId: response.data.submitTransaction };
|
248
328
|
} catch (e) {
|
249
329
|
return { success: false, message: (e as Error).message };
|
@@ -251,9 +331,7 @@ export class ErgoGraphQLProvider implements IBlockchainProvider<BoxWhere> {
|
|
251
331
|
}
|
252
332
|
|
253
333
|
reduceTransaction(): Promise<TransactionReductionResult> {
|
254
|
-
throw new NotSupportedError(
|
255
|
-
"Transaction reducing is not supported by ergo-graphql."
|
256
|
-
);
|
334
|
+
throw new NotSupportedError("Transaction reducing is not supported by ergo-graphql.");
|
257
335
|
}
|
258
336
|
}
|
259
337
|
|
@@ -272,18 +350,45 @@ function buildGqlBoxQueryArgs(where: GraphQLBoxWhere) {
|
|
272
350
|
if (some(addresses)) {
|
273
351
|
const trees = addresses.map((address) =>
|
274
352
|
typeof address === "string"
|
275
|
-
? ErgoAddress.
|
353
|
+
? ErgoAddress.decode(address).ergoTree
|
276
354
|
: address.ergoTree
|
277
355
|
);
|
278
356
|
|
279
|
-
args.ergoTrees = uniq(
|
280
|
-
some(args.ergoTrees) ? args.ergoTrees.concat(trees) : trees
|
281
|
-
);
|
357
|
+
args.ergoTrees = uniq(some(args.ergoTrees) ? args.ergoTrees.concat(trees) : trees);
|
282
358
|
}
|
283
359
|
|
284
360
|
return args;
|
285
361
|
}
|
286
362
|
|
363
|
+
function buildGqlUnconfirmedTxQueryArgs(where: GraphQLConfirmedTransactionWhere) {
|
364
|
+
const addresses = uniq(
|
365
|
+
[
|
366
|
+
merge(where.addresses, where.address)?.map((address): string =>
|
367
|
+
typeof address === "string" ? address : address.encode()
|
368
|
+
) ?? [],
|
369
|
+
merge(where.ergoTrees, where.ergoTree)?.map((tree) =>
|
370
|
+
ErgoAddress.fromErgoTree(tree).encode()
|
371
|
+
) ?? []
|
372
|
+
].flat()
|
373
|
+
);
|
374
|
+
|
375
|
+
return {
|
376
|
+
addresses: addresses.length ? addresses : undefined,
|
377
|
+
transactionIds: merge(where.transactionIds, where.transactionId),
|
378
|
+
skip: 0,
|
379
|
+
take: PAGE_SIZE
|
380
|
+
};
|
381
|
+
}
|
382
|
+
|
383
|
+
function buildGqlConfirmedTxQueryArgs(where: GraphQLConfirmedTransactionWhere) {
|
384
|
+
return {
|
385
|
+
...buildGqlUnconfirmedTxQueryArgs(where),
|
386
|
+
headerId: where.headerId,
|
387
|
+
minHeight: where.minHeight,
|
388
|
+
onlyRelevantOutputs: where.onlyRelevantOutputs
|
389
|
+
};
|
390
|
+
}
|
391
|
+
|
287
392
|
function merge<T>(array?: T[], el?: T) {
|
288
393
|
if (isEmpty(array) && isUndefined(el)) return;
|
289
394
|
|
@@ -300,14 +405,79 @@ function hasConfirmed(data: unknown): data is ConfirmedBoxesResponse {
|
|
300
405
|
return !!(data as ConfirmedBoxesResponse)?.boxes;
|
301
406
|
}
|
302
407
|
|
303
|
-
function
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
408
|
+
function mapConfirmedBox<T>(box: GQLBox, mapper: BiMapper<T>): ChainProviderBox<T> {
|
409
|
+
const mapped = mapBox(box, mapper) as ChainProviderBox<T>;
|
410
|
+
mapped.confirmed = true;
|
411
|
+
return mapped;
|
412
|
+
}
|
413
|
+
|
414
|
+
function mapUnconfirmedBox<T>(box: GQLBox, mapper: BiMapper<T>): ChainProviderBox<T> {
|
415
|
+
const mapped = mapBox(box, mapper) as ChainProviderBox<T>;
|
416
|
+
mapped.confirmed = false;
|
417
|
+
return mapped;
|
418
|
+
}
|
419
|
+
|
420
|
+
function mapBox<T>(
|
421
|
+
box: GQLBox | GQLUnconfirmedBox,
|
422
|
+
mapper: BiMapper<T>
|
423
|
+
): Omit<ChainProviderBox<T>, "confirmed"> {
|
424
|
+
return {
|
425
|
+
boxId: box.boxId,
|
426
|
+
transactionId: box.transactionId,
|
427
|
+
value: mapper(box.value),
|
428
|
+
ergoTree: box.ergoTree,
|
429
|
+
assets: box.assets.map((t) => ({ tokenId: t.tokenId, amount: mapper(t.amount) })),
|
430
|
+
creationHeight: box.creationHeight,
|
431
|
+
additionalRegisters: box.additionalRegisters,
|
432
|
+
index: box.index
|
433
|
+
};
|
434
|
+
}
|
435
|
+
|
436
|
+
function mapUnconfirmedTransaction<T>(
|
437
|
+
tx: UnconfirmedTransaction,
|
438
|
+
mapper: BiMapper<T>
|
439
|
+
): ChainProviderUnconfirmedTransaction<T> {
|
440
|
+
return {
|
441
|
+
transactionId: tx.transactionId,
|
442
|
+
timestamp: Number(tx.timestamp),
|
443
|
+
inputs: tx.inputs.map((i) => ({
|
444
|
+
spendingProof: {
|
445
|
+
// biome-ignore lint/style/noNonNullAssertion: bad type declarations at '@ergo-graphql/type'
|
446
|
+
extension: i.extension!,
|
447
|
+
// biome-ignore lint/style/noNonNullAssertion: bad type declarations at '@ergo-graphql/type'
|
448
|
+
proofBytes: i.proofBytes!
|
449
|
+
},
|
450
|
+
// biome-ignore lint/style/noNonNullAssertion: bad type declarations at '@ergo-graphql/type'
|
451
|
+
...mapBox(i.box!, mapper)
|
452
|
+
})),
|
453
|
+
dataInputs: tx.dataInputs.map((di) => ({ boxId: di.boxId })),
|
454
|
+
outputs: tx.outputs.map((b) => mapBox(b, mapper)),
|
455
|
+
confirmed: false
|
456
|
+
};
|
457
|
+
}
|
458
|
+
|
459
|
+
function mapConfirmedTransaction<T>(
|
460
|
+
tx: Transaction,
|
461
|
+
mapper: BiMapper<T>
|
462
|
+
): ChainProviderConfirmedTransaction<T> {
|
463
|
+
return {
|
464
|
+
transactionId: tx.transactionId,
|
465
|
+
timestamp: Number(tx.timestamp),
|
466
|
+
inputs: tx.inputs.map((i) => ({
|
467
|
+
spendingProof: {
|
468
|
+
// biome-ignore lint/style/noNonNullAssertion: bad type declarations at '@ergo-graphql/type'
|
469
|
+
extension: i.extension!,
|
470
|
+
// biome-ignore lint/style/noNonNullAssertion: bad type declarations at '@ergo-graphql/type'
|
471
|
+
proofBytes: i.proofBytes!
|
472
|
+
},
|
473
|
+
// biome-ignore lint/style/noNonNullAssertion: bad type declarations at '@ergo-graphql/type'
|
474
|
+
...mapBox(i.box!, mapper)
|
310
475
|
})),
|
311
|
-
|
312
|
-
|
476
|
+
dataInputs: tx.dataInputs.map((di) => ({ boxId: di.boxId })),
|
477
|
+
outputs: tx.outputs.map((b) => mapBox(b, mapper)),
|
478
|
+
height: tx.inclusionHeight,
|
479
|
+
headerId: tx.headerId,
|
480
|
+
index: tx.index,
|
481
|
+
confirmed: true
|
482
|
+
};
|
313
483
|
}
|
@@ -1,15 +1,24 @@
|
|
1
1
|
const B = [
|
2
|
-
"
|
2
|
+
"$boxIds: [String!] $ergoTrees: [String!] $ergoTreeTemplateHash: String $tokenId: String $skip: Int $take: Int",
|
3
3
|
"boxIds: $boxIds ergoTrees: $ergoTrees ergoTreeTemplateHash: $ergoTreeTemplateHash tokenId: $tokenId skip: $skip take: $take",
|
4
|
-
"boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters
|
4
|
+
"boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters"
|
5
5
|
];
|
6
6
|
|
7
|
-
export const CONF_BOXES_QUERY =
|
8
|
-
export const UNCONF_BOXES_QUERY =
|
9
|
-
export const ALL_BOXES_QUERY =
|
7
|
+
export const CONF_BOXES_QUERY = `query boxes($spent: Boolean! ${B[0]}) { boxes(spent: $spent ${B[1]}) { ${B[2]} beingSpent } }`;
|
8
|
+
export const UNCONF_BOXES_QUERY = `query boxes(${B[0]}) { mempool { boxes(${B[1]}) { ${B[2]} beingSpent } } }`;
|
9
|
+
export const ALL_BOXES_QUERY = `query boxes($spent: Boolean! ${B[0]}) { boxes(spent: $spent ${B[1]}) { ${B[2]} beingSpent } mempool { boxes(${B[1]}) { ${B[2]} beingSpent } } }`;
|
10
|
+
|
10
11
|
export const HEADERS_QUERY =
|
11
12
|
"query blockHeaders($take: Int) { blockHeaders(take: $take) {headerId timestamp version adProofsRoot stateRoot transactionsRoot nBits extensionHash powSolutions height difficulty parentId votes } }";
|
12
13
|
export const CHECK_TX_MUTATION =
|
13
14
|
"mutation checkTransaction($signedTransaction: SignedTransaction!) { checkTransaction(signedTransaction: $signedTransaction) }";
|
14
15
|
export const SEND_TX_MUTATION =
|
15
16
|
"mutation submitTransaction($signedTransaction: SignedTransaction!) { submitTransaction(signedTransaction: $signedTransaction) }";
|
17
|
+
|
18
|
+
const T = [
|
19
|
+
"$addresses: [String!], $transactionIds: [String!], $skip: Int, $take: Int",
|
20
|
+
"addresses: $addresses, transactionIds: $transactionIds, skip: $skip, take: $take",
|
21
|
+
`transactionId timestamp inputs { proofBytes extension index box { ${B[2]} } } dataInputs { boxId }`
|
22
|
+
];
|
23
|
+
export const CONF_TX_QUERY = `query confirmedTransactions(${T[0]} $relevantOnly: Boolean) { transactions(${T[1]}) { ${T[2]} outputs(relevantOnly: $relevantOnly) { ${B[2]} } inclusionHeight headerId index } }`;
|
24
|
+
export const UNCONF_TX_QUERY = `query unconfirmedTransactions(${T[0]}) { mempool { transactions(${T[1]}) { ${T[2]} outputs { ${B[2]} } } } }`;
|
@@ -3,7 +3,9 @@ import type {
|
|
3
3
|
BlockHeader,
|
4
4
|
Box,
|
5
5
|
BoxId,
|
6
|
+
DataInput,
|
6
7
|
HexString,
|
8
|
+
ProverResult,
|
7
9
|
SignedTransaction,
|
8
10
|
TokenId,
|
9
11
|
TransactionId,
|
@@ -14,6 +16,23 @@ import type { RequireAtLeastOne } from "type-fest";
|
|
14
16
|
|
15
17
|
export type BoxSource = "blockchain" | "mempool" | "blockchain+mempool";
|
16
18
|
|
19
|
+
export type BoxWhere = {
|
20
|
+
/** Base16-encoded BoxId */
|
21
|
+
boxId?: BoxId;
|
22
|
+
|
23
|
+
/** Base16-encoded ErgoTree */
|
24
|
+
ergoTree?: HexString;
|
25
|
+
|
26
|
+
/** Base58-encoded address */
|
27
|
+
address?: ErgoAddress | Base58String;
|
28
|
+
|
29
|
+
/** Base16-encoded contract template hash */
|
30
|
+
templateHash?: HexString;
|
31
|
+
|
32
|
+
/** Base16-encoded TokenId */
|
33
|
+
tokenId?: TokenId;
|
34
|
+
};
|
35
|
+
|
17
36
|
export type BoxQuery<W extends BoxWhere> = {
|
18
37
|
/** The query to filter boxes. */
|
19
38
|
where: RequireAtLeastOne<W>;
|
@@ -25,29 +44,69 @@ export type BoxQuery<W extends BoxWhere> = {
|
|
25
44
|
from?: BoxSource;
|
26
45
|
};
|
27
46
|
|
28
|
-
export type
|
47
|
+
export type UnconfirmedTransactionWhere = {
|
48
|
+
/** Base16-encoded TransactionId */
|
49
|
+
transactionId?: TransactionId;
|
29
50
|
|
30
|
-
|
31
|
-
|
32
|
-
boxId?: BoxId;
|
51
|
+
/** Base58-encoded address */
|
52
|
+
address?: ErgoAddress | Base58String;
|
33
53
|
|
34
54
|
/** Base16-encoded ErgoTree */
|
35
55
|
ergoTree?: HexString;
|
56
|
+
};
|
57
|
+
|
58
|
+
export type ConfirmedTransactionWhere = {
|
59
|
+
/** Base16-encoded TransactionId */
|
60
|
+
transactionId?: TransactionId;
|
61
|
+
|
62
|
+
/** Base16-encoded HeaderID */
|
63
|
+
headerId?: HexString;
|
36
64
|
|
37
65
|
/** Base58-encoded address */
|
38
66
|
address?: ErgoAddress | Base58String;
|
39
67
|
|
40
|
-
/** Base16-encoded
|
41
|
-
|
68
|
+
/** Base16-encoded ErgoTree */
|
69
|
+
ergoTree?: HexString;
|
42
70
|
|
43
|
-
/**
|
44
|
-
|
71
|
+
/** Min blockchain height */
|
72
|
+
minHeight?: number;
|
73
|
+
|
74
|
+
/** Only returns relevant outputs for the selected filter params */
|
75
|
+
onlyRelevantOutputs?: boolean;
|
76
|
+
};
|
77
|
+
|
78
|
+
export type TransactionQuery<W extends ConfirmedTransactionWhere> = {
|
79
|
+
/** The query to filter boxes. */
|
80
|
+
where: RequireAtLeastOne<W, keyof Omit<W, "minHeight" | "onlyRelevantOutputs">>;
|
45
81
|
};
|
46
82
|
|
47
|
-
export type
|
83
|
+
export type HeaderQuery = { take: number };
|
84
|
+
|
85
|
+
export type ChainProviderBox<T> = Omit<Box, "value" | "assets"> & {
|
86
|
+
value: T;
|
87
|
+
assets: { tokenId: TokenId; amount: T }[];
|
88
|
+
confirmed: boolean;
|
89
|
+
};
|
90
|
+
|
91
|
+
type TransactionOutput<T> = Omit<ChainProviderBox<T>, "confirmed">;
|
92
|
+
type TransactionInput<T> = TransactionOutput<T> & { spendingProof: ProverResult };
|
93
|
+
|
94
|
+
export type ChainProviderUnconfirmedTransaction<T> = {
|
95
|
+
transactionId: TransactionId;
|
96
|
+
inputs: TransactionInput<T>[];
|
97
|
+
dataInputs: DataInput[];
|
98
|
+
outputs: TransactionOutput<T>[];
|
48
99
|
confirmed: boolean;
|
100
|
+
timestamp: number;
|
49
101
|
};
|
50
102
|
|
103
|
+
export type ChainProviderConfirmedTransaction<T> =
|
104
|
+
ChainProviderUnconfirmedTransaction<T> & {
|
105
|
+
height: number;
|
106
|
+
index: number;
|
107
|
+
headerId: HexString;
|
108
|
+
};
|
109
|
+
|
51
110
|
export type TransactionEvaluationError = {
|
52
111
|
success: false;
|
53
112
|
message: string;
|
@@ -74,16 +133,44 @@ export type TransactionReductionResult =
|
|
74
133
|
* Represents a blockchain provider that can interact with the blockchain.
|
75
134
|
* @template B The type of the box query used by the provider.
|
76
135
|
*/
|
77
|
-
export interface IBlockchainProvider<
|
136
|
+
export interface IBlockchainProvider<I> {
|
78
137
|
/**
|
79
138
|
* Get boxes.
|
80
139
|
*/
|
81
|
-
getBoxes(query: BoxQuery<
|
140
|
+
getBoxes(query: BoxQuery<BoxWhere>): Promise<ChainProviderBox<I>[]>;
|
82
141
|
|
83
142
|
/**
|
84
143
|
* Stream boxes.
|
85
144
|
*/
|
86
|
-
streamBoxes(query: BoxQuery<
|
145
|
+
streamBoxes(query: BoxQuery<BoxWhere>): AsyncIterable<ChainProviderBox<I>[]>;
|
146
|
+
|
147
|
+
/**
|
148
|
+
* Stream unconfirmed transactions
|
149
|
+
*/
|
150
|
+
streamUnconfirmedTransactions(
|
151
|
+
query: TransactionQuery<UnconfirmedTransactionWhere>
|
152
|
+
): AsyncIterable<ChainProviderUnconfirmedTransaction<I>[]>;
|
153
|
+
|
154
|
+
/**
|
155
|
+
* Get unconfirmed transactions
|
156
|
+
*/
|
157
|
+
getUnconfirmedTransactions(
|
158
|
+
query: TransactionQuery<UnconfirmedTransactionWhere>
|
159
|
+
): Promise<ChainProviderUnconfirmedTransaction<I>[]>;
|
160
|
+
|
161
|
+
/**
|
162
|
+
* Stream confirmed transactions
|
163
|
+
*/
|
164
|
+
streamConfirmedTransactions(
|
165
|
+
query: TransactionQuery<ConfirmedTransactionWhere>
|
166
|
+
): AsyncIterable<ChainProviderConfirmedTransaction<I>[]>;
|
167
|
+
|
168
|
+
/**
|
169
|
+
* Get confirmed transactions
|
170
|
+
*/
|
171
|
+
getConfirmedTransactions(
|
172
|
+
query: TransactionQuery<ConfirmedTransactionWhere>
|
173
|
+
): Promise<ChainProviderConfirmedTransaction<I>[]>;
|
87
174
|
|
88
175
|
/**
|
89
176
|
* Get headers.
|
@@ -93,16 +180,12 @@ export interface IBlockchainProvider<B extends BoxWhere> {
|
|
93
180
|
/**
|
94
181
|
* Check for transaction validity without broadcasting it to the network.
|
95
182
|
*/
|
96
|
-
checkTransaction(
|
97
|
-
transaction: SignedTransaction
|
98
|
-
): Promise<TransactionEvaluationResult>;
|
183
|
+
checkTransaction(transaction: SignedTransaction): Promise<TransactionEvaluationResult>;
|
99
184
|
|
100
185
|
/**
|
101
186
|
* Broadcast a transaction to the network.
|
102
187
|
*/
|
103
|
-
submitTransaction(
|
104
|
-
transaction: SignedTransaction
|
105
|
-
): Promise<TransactionEvaluationResult>;
|
188
|
+
submitTransaction(transaction: SignedTransaction): Promise<TransactionEvaluationResult>;
|
106
189
|
|
107
190
|
/**
|
108
191
|
* Evaluate a transaction and return Base16-encoded evaluation result.
|