@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.
@@ -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
- TransactionReductionResult
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: Box[] };
67
- type UnconfirmedBoxesResponse = { mempool: { boxes: Box[] } };
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<BoxWhere> {
105
+ export class ErgoGraphQLProvider<I = bigint> implements IBlockchainProvider<I> {
77
106
  #options: GraphQLThrowableOptions;
78
-
79
- #getConfBoxes;
80
- #getUnconfBoxes;
81
- #getAllBoxes;
82
- #getHeaders;
83
- #checkTx;
84
- #sendTx;
85
-
86
- constructor(url: string | URL);
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 | URL) {
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.#getConfBoxes = this.createOperation<
95
- ConfirmedBoxesResponse,
96
- QueryBoxesArgs
97
- >(CONF_BOXES_QUERY);
98
-
99
- this.#getUnconfBoxes = this.createOperation<
100
- UnconfirmedBoxesResponse,
101
- QueryBoxesArgs
102
- >(UNCONF_BOXES_QUERY);
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.#getUnconfBoxes(args)
130
- : this.#getConfBoxes(args);
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: Box) => !box.beingSpent;
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 response = await this.#fetchBoxes(args, inclChain, inclPool);
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(asConfirmed(true));
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(asConfirmed(false));
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
- let boxes: ChainProviderBox[] = [];
198
- for await (const chunk of this.streamBoxes(query)) {
199
- boxes = boxes.concat(chunk);
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 orderBy(boxes, (box) => box.creationHeight);
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.#checkTx({ signedTransaction });
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.#sendTx({ signedTransaction });
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.fromBase58(address).ergoTree
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 asConfirmed(confirmed: boolean) {
304
- return (box: Box): ChainProviderBox => ({
305
- ...box,
306
- value: BigInt(box.value),
307
- assets: box.assets.map((asset) => ({
308
- tokenId: asset.tokenId,
309
- amount: BigInt(asset.amount)
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
- confirmed
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
- "query boxes($spent: Boolean! $boxIds: [String!] $ergoTrees: [String!] $ergoTreeTemplateHash: String $tokenId: String $skip: Int $take: Int)",
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 beingSpent"
4
+ "boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters"
5
5
  ];
6
6
 
7
- export const CONF_BOXES_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } }`;
8
- export const UNCONF_BOXES_QUERY = `${B[0]} { mempool { boxes(${B[1]}) { ${B[2]} } } }`;
9
- export const ALL_BOXES_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } mempool { boxes(${B[1]}) { ${B[2]} } } }`;
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 HeaderQuery = { take: number };
47
+ export type UnconfirmedTransactionWhere = {
48
+ /** Base16-encoded TransactionId */
49
+ transactionId?: TransactionId;
29
50
 
30
- export type BoxWhere = {
31
- /** Base16-encoded BoxId */
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 contract template hash */
41
- templateHash?: HexString;
68
+ /** Base16-encoded ErgoTree */
69
+ ergoTree?: HexString;
42
70
 
43
- /** Base16-encoded TokenId */
44
- tokenId?: TokenId;
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 ChainProviderBox = Box<bigint> & {
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<B extends BoxWhere> {
136
+ export interface IBlockchainProvider<I> {
78
137
  /**
79
138
  * Get boxes.
80
139
  */
81
- getBoxes(query: BoxQuery<B>): Promise<ChainProviderBox[]>;
140
+ getBoxes(query: BoxQuery<BoxWhere>): Promise<ChainProviderBox<I>[]>;
82
141
 
83
142
  /**
84
143
  * Stream boxes.
85
144
  */
86
- streamBoxes(query: BoxQuery<B>): AsyncIterable<ChainProviderBox[]>;
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.