@fleet-sdk/blockchain-providers 0.3.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 +15 -0
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/index.cjs.js +215 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.mts +281 -0
- package/dist/index.d.ts +281 -0
- package/dist/index.esm.js +213 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +47 -0
- package/src/ergo-graphql/ergoGraphQLProvider.ts +280 -0
- package/src/ergo-graphql/index.ts +1 -0
- package/src/ergo-graphql/queries.ts +12 -0
- package/src/index.ts +1 -0
- package/src/types/blockchainProvider.ts +103 -0
- package/src/types/index.ts +1 -0
- package/src/utils/_tests.ts +8 -0
- package/src/utils/graphql.test-d.ts +23 -0
- package/src/utils/graphql.ts +110 -0
- package/src/utils/index.ts +2 -0
package/CHANGELOG.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# @fleet-sdk/blockchain-providers
|
2
|
+
|
3
|
+
## 0.3.0
|
4
|
+
|
5
|
+
### Minor Changes
|
6
|
+
|
7
|
+
- feb2109: Introduce `@fleet-sdk/blockchain-providers` package with `ErgoGraphQLProvider`
|
8
|
+
|
9
|
+
### Patch Changes
|
10
|
+
|
11
|
+
- Updated dependencies [d05b74d]
|
12
|
+
- Updated dependencies [c42ca0d]
|
13
|
+
- Updated dependencies [d08255c]
|
14
|
+
- @fleet-sdk/common@0.3.0
|
15
|
+
- @fleet-sdk/core@0.3.0
|
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Nautilus Team
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# @fleet-sdk/blockchain-providers [](https://github.com/fleet-sdk/fleet/blob/master/LICENSE) [](https://www.npmjs.com/package/@fleet-sdk/blockchain-providers)
|
2
|
+
|
3
|
+
Ergo Blockchain data providers.
|
4
|
+
|
5
|
+
## Build
|
6
|
+
|
7
|
+
```sh
|
8
|
+
pnpm --filter blockchain-providers build
|
9
|
+
```
|
10
|
+
|
11
|
+
## Test
|
12
|
+
|
13
|
+
```sh
|
14
|
+
pnpm test:unit blockchain-providers
|
15
|
+
```
|
@@ -0,0 +1,215 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var common = require('@fleet-sdk/common');
|
4
|
+
var core = require('@fleet-sdk/core');
|
5
|
+
|
6
|
+
// src/ergo-graphql/ergoGraphQLProvider.ts
|
7
|
+
var OP_NAME_REGEX = /(query|mutation)\s?([\w\-_]+)?/;
|
8
|
+
var DEFAULT_HEADERS = {
|
9
|
+
"content-type": "application/json; charset=utf-8",
|
10
|
+
accept: "application/graphql-response+json, application/json"
|
11
|
+
};
|
12
|
+
function createGqlOperation(query, options) {
|
13
|
+
return async (variables) => {
|
14
|
+
const response = await (options.fetcher ?? fetch)(options.url, {
|
15
|
+
method: "POST",
|
16
|
+
headers: common.ensureDefaults(options.headers, DEFAULT_HEADERS),
|
17
|
+
credentials: options.credentials,
|
18
|
+
body: (options.parser ?? JSON).stringify({
|
19
|
+
operationName: getOpName(query),
|
20
|
+
query,
|
21
|
+
variables: variables ? common.clearUndefined(variables) : void 0
|
22
|
+
})
|
23
|
+
});
|
24
|
+
const rawData = await response.text();
|
25
|
+
const parsedData = (options.parser ?? JSON).parse(rawData);
|
26
|
+
if (options.throwOnNonNetworkErrors && common.some(parsedData.errors) && common.isEmpty(parsedData.data)) {
|
27
|
+
throw new common.BlockchainProviderError(parsedData.errors[0].message, { cause: parsedData.errors });
|
28
|
+
}
|
29
|
+
return parsedData;
|
30
|
+
};
|
31
|
+
}
|
32
|
+
function getOpName(query) {
|
33
|
+
return OP_NAME_REGEX.exec(query)?.at(2);
|
34
|
+
}
|
35
|
+
function isRequestParam(obj) {
|
36
|
+
return typeof obj === "object" && obj.url !== void 0;
|
37
|
+
}
|
38
|
+
|
39
|
+
// src/ergo-graphql/queries.ts
|
40
|
+
var B = [
|
41
|
+
`query boxes($spent: Boolean! $boxIds: [String!] $ergoTrees: [String!] $ergoTreeTemplateHash: String $tokenId: String $skip: Int $take: Int)`,
|
42
|
+
`boxIds: $boxIds ergoTrees: $ergoTrees ergoTreeTemplateHash: $ergoTreeTemplateHash tokenId: $tokenId skip: $skip take: $take`,
|
43
|
+
`boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters beingSpent`
|
44
|
+
];
|
45
|
+
var CONF_BOXES_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } }`;
|
46
|
+
var UNCONF_BOXES_QUERY = `${B[0]} { mempool { boxes(${B[1]}) { ${B[2]} } } }`;
|
47
|
+
var ALL_BOXES_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } mempool { boxes(${B[1]}) { ${B[2]} } } }`;
|
48
|
+
var HEADERS_QUERY = `query blockHeaders($take: Int) { blockHeaders(take: $take) {headerId timestamp version adProofsRoot stateRoot transactionsRoot nBits extensionHash powSolutions height difficulty parentId votes } }`;
|
49
|
+
var CHECK_TX_MUTATION = `mutation checkTransaction($signedTransaction: SignedTransaction!) { checkTransaction(signedTransaction: $signedTransaction) }`;
|
50
|
+
var SEND_TX_MUTATION = `mutation submitTransaction($signedTransaction: SignedTransaction!) { submitTransaction(signedTransaction: $signedTransaction) }`;
|
51
|
+
|
52
|
+
// src/ergo-graphql/ergoGraphQLProvider.ts
|
53
|
+
var PAGE_SIZE = 50;
|
54
|
+
var ErgoGraphQLProvider = class {
|
55
|
+
#options;
|
56
|
+
#getConfBoxes;
|
57
|
+
#getUnconfBoxes;
|
58
|
+
#getAllBoxes;
|
59
|
+
#getHeaders;
|
60
|
+
#checkTx;
|
61
|
+
#sendTx;
|
62
|
+
constructor(optOrUrl) {
|
63
|
+
this.#options = {
|
64
|
+
...isRequestParam(optOrUrl) ? optOrUrl : { url: optOrUrl },
|
65
|
+
throwOnNonNetworkErrors: true
|
66
|
+
};
|
67
|
+
this.#getConfBoxes = this.createOperation(CONF_BOXES_QUERY);
|
68
|
+
this.#getUnconfBoxes = this.createOperation(UNCONF_BOXES_QUERY);
|
69
|
+
this.#getAllBoxes = this.createOperation(ALL_BOXES_QUERY);
|
70
|
+
this.#getHeaders = this.createOperation(HEADERS_QUERY);
|
71
|
+
this.#checkTx = this.createOperation(CHECK_TX_MUTATION);
|
72
|
+
this.#sendTx = this.createOperation(SEND_TX_MUTATION);
|
73
|
+
}
|
74
|
+
#fetchBoxes(args, inclConf, inclUnconf) {
|
75
|
+
if (inclConf && inclUnconf) {
|
76
|
+
return this.#getAllBoxes(args);
|
77
|
+
} else if (inclUnconf) {
|
78
|
+
return this.#getUnconfBoxes(args);
|
79
|
+
} else {
|
80
|
+
return this.#getConfBoxes(args);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
async *streamBoxes(query) {
|
84
|
+
if (common.isEmpty(query.where)) {
|
85
|
+
throw new Error("Cannot fetch unspent boxes without a where clause.");
|
86
|
+
}
|
87
|
+
const notBeingSpent = (box) => !box.beingSpent;
|
88
|
+
const returnedBoxIds = /* @__PURE__ */ new Set();
|
89
|
+
const { where, from } = query;
|
90
|
+
const args = buildGqlBoxQueryArgs(where);
|
91
|
+
let fetchFromChain = from !== "mempool";
|
92
|
+
let fetchFromMempool = from !== "blockchain";
|
93
|
+
const isMempoolAware = fetchFromMempool;
|
94
|
+
do {
|
95
|
+
const response = await this.#fetchBoxes(args, fetchFromChain, fetchFromMempool);
|
96
|
+
const { data } = response;
|
97
|
+
let boxes = [];
|
98
|
+
if (fetchFromChain && hasConfirmed(data)) {
|
99
|
+
if (common.some(data.boxes)) {
|
100
|
+
const confirmedBoxes = (isMempoolAware ? data.boxes.filter(notBeingSpent) : data.boxes).map(asConfirmed(true));
|
101
|
+
boxes = boxes.concat(confirmedBoxes);
|
102
|
+
}
|
103
|
+
fetchFromChain = data.boxes.length === PAGE_SIZE;
|
104
|
+
}
|
105
|
+
if (isMempoolAware && hasMempool(data)) {
|
106
|
+
if (common.some(data.mempool.boxes)) {
|
107
|
+
const mempoolBoxes = data.mempool.boxes.filter(notBeingSpent).map(asConfirmed(false));
|
108
|
+
boxes = boxes.concat(mempoolBoxes);
|
109
|
+
}
|
110
|
+
fetchFromMempool = data.mempool.boxes.length === PAGE_SIZE;
|
111
|
+
}
|
112
|
+
if (common.some(boxes)) {
|
113
|
+
if (boxes.some((box) => returnedBoxIds.has(box.boxId))) {
|
114
|
+
boxes = boxes.filter((b) => !returnedBoxIds.has(b.boxId));
|
115
|
+
}
|
116
|
+
if (common.some(boxes)) {
|
117
|
+
boxes = common.uniqBy(boxes, (box) => box.boxId);
|
118
|
+
boxes.forEach((box) => returnedBoxIds.add(box.boxId));
|
119
|
+
yield boxes;
|
120
|
+
}
|
121
|
+
}
|
122
|
+
if (fetchFromChain || fetchFromMempool)
|
123
|
+
args.skip += PAGE_SIZE;
|
124
|
+
} while (fetchFromChain || fetchFromMempool);
|
125
|
+
}
|
126
|
+
async getBoxes(query) {
|
127
|
+
let boxes = [];
|
128
|
+
for await (const chunk of this.streamBoxes(query)) {
|
129
|
+
boxes = boxes.concat(chunk);
|
130
|
+
}
|
131
|
+
return common.orderBy(boxes, (box) => box.creationHeight);
|
132
|
+
}
|
133
|
+
async getHeaders(query) {
|
134
|
+
const response = await this.#getHeaders(query);
|
135
|
+
return response.data?.blockHeaders.map((header) => ({
|
136
|
+
...header,
|
137
|
+
id: header.headerId,
|
138
|
+
timestamp: Number(header.timestamp),
|
139
|
+
nBits: Number(header.nBits),
|
140
|
+
votes: header.votes.join("")
|
141
|
+
})) ?? [];
|
142
|
+
}
|
143
|
+
createOperation(query, options) {
|
144
|
+
const opt = common.ensureDefaults(options, this.#options);
|
145
|
+
opt.throwOnNonNetworkErrors = true;
|
146
|
+
return createGqlOperation(query, opt);
|
147
|
+
}
|
148
|
+
async checkTransaction(signedTransaction) {
|
149
|
+
try {
|
150
|
+
const response = await this.#checkTx({ signedTransaction });
|
151
|
+
return { success: true, transactionId: response.data.checkTransaction };
|
152
|
+
} catch (e) {
|
153
|
+
return { success: false, message: e.message };
|
154
|
+
}
|
155
|
+
}
|
156
|
+
async submitTransaction(signedTransaction) {
|
157
|
+
try {
|
158
|
+
const response = await this.#sendTx({ signedTransaction });
|
159
|
+
return { success: true, transactionId: response.data.submitTransaction };
|
160
|
+
} catch (e) {
|
161
|
+
return { success: false, message: e.message };
|
162
|
+
}
|
163
|
+
}
|
164
|
+
reduceTransaction() {
|
165
|
+
throw new common.NotSupportedError("Transaction reducing is not supported by ergo-graphql.");
|
166
|
+
}
|
167
|
+
};
|
168
|
+
function buildGqlBoxQueryArgs(where) {
|
169
|
+
const args = {
|
170
|
+
spent: false,
|
171
|
+
boxIds: merge(where.boxIds, where.boxId),
|
172
|
+
ergoTrees: merge(where.ergoTrees, where.ergoTree),
|
173
|
+
ergoTreeTemplateHash: where.templateHash,
|
174
|
+
tokenId: where.tokenId,
|
175
|
+
skip: 0,
|
176
|
+
take: PAGE_SIZE
|
177
|
+
};
|
178
|
+
const addresses = merge(where.addresses, where.address);
|
179
|
+
if (common.some(addresses)) {
|
180
|
+
const trees = addresses.map(
|
181
|
+
(address) => typeof address === "string" ? core.ErgoAddress.fromBase58(address).ergoTree : address.ergoTree
|
182
|
+
);
|
183
|
+
args.ergoTrees = common.uniq(common.some(args.ergoTrees) ? args.ergoTrees.concat(trees) : trees);
|
184
|
+
}
|
185
|
+
return args;
|
186
|
+
}
|
187
|
+
function merge(array, el) {
|
188
|
+
if (common.isEmpty(array) && common.isUndefined(el))
|
189
|
+
return;
|
190
|
+
const set = new Set(array ?? []);
|
191
|
+
if (!common.isUndefined(el))
|
192
|
+
set.add(el);
|
193
|
+
return Array.from(set.values());
|
194
|
+
}
|
195
|
+
function hasMempool(data) {
|
196
|
+
return !!data?.mempool?.boxes;
|
197
|
+
}
|
198
|
+
function hasConfirmed(data) {
|
199
|
+
return !!data?.boxes;
|
200
|
+
}
|
201
|
+
function asConfirmed(confirmed) {
|
202
|
+
return (box) => ({
|
203
|
+
...box,
|
204
|
+
value: BigInt(box.value),
|
205
|
+
assets: box.assets.map((asset) => ({
|
206
|
+
tokenId: asset.tokenId,
|
207
|
+
amount: BigInt(asset.amount)
|
208
|
+
})),
|
209
|
+
confirmed
|
210
|
+
});
|
211
|
+
}
|
212
|
+
|
213
|
+
exports.ErgoGraphQLProvider = ErgoGraphQLProvider;
|
214
|
+
//# sourceMappingURL=out.js.map
|
215
|
+
//# sourceMappingURL=index.cjs.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["../src/ergo-graphql/ergoGraphQLProvider.ts","../src/utils/graphql.ts","../src/ergo-graphql/queries.ts"],"names":["ensureDefaults","isEmpty","some"],"mappings":";AAMA;AAAA,EAGE,kBAAAA;AAAA,EAEA,WAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,QAAAC;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAmB;;;ACpB5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,IAAM,gBAAgB;AACf,IAAM,kBAA2B;AAAA,EACtC,gBAAgB;AAAA,EAChB,QAAQ;AACV;AA4DO,SAAS,mBACd,OACA,SACyC;AACzC,SAAO,OAAO,cAA+C;AAC3D,UAAM,WAAW,OAAO,QAAQ,WAAW,OAAO,QAAQ,KAAK;AAAA,MAC7D,QAAQ;AAAA,MACR,SAAS,eAAe,QAAQ,SAAS,eAAe;AAAA,MACxD,aAAa,QAAQ;AAAA,MACrB,OAAO,QAAQ,UAAU,MAAM,UAAU;AAAA,QACvC,eAAe,UAAU,KAAK;AAAA,QAC9B;AAAA,QACA,WAAW,YAAY,eAAe,SAAS,IAAI;AAAA,MACrD,CAAkB;AAAA,IACpB,CAAC;AAED,UAAM,UAAU,MAAM,SAAS,KAAK;AACpC,UAAM,cAAc,QAAQ,UAAU,MAAM,MAAM,OAAO;AAEzD,QAAI,QAAQ,2BAA2B,KAAK,WAAW,MAAM,KAAK,QAAQ,WAAW,IAAI,GAAG;AAC1F,YAAM,IAAI,wBAAwB,WAAW,OAAO,CAAC,EAAE,SAAS,EAAE,OAAO,WAAW,OAAO,CAAC;AAAA,IAC9F;AAEA,WAAO;AAAA,EACT;AACF;AAMO,SAAS,UAAU,OAAmC;AAC3D,SAAO,cAAc,KAAK,KAAK,GAAG,GAAG,CAAC;AACxC;AAEO,SAAS,eAAe,KAA4C;AACzE,SAAO,OAAO,QAAQ,YAAa,IAA8B,QAAQ;AAC3E;;;AC7GA,IAAM,IAAI;AAAA,EACR;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAAmB,GAAG,EAAE,CAAC,CAAC,0BAA0B,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AACzE,IAAM,qBAAqB,GAAG,EAAE,CAAC,CAAC,sBAAsB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AACvE,IAAM,kBAAkB,GAAG,EAAE,CAAC,CAAC,0BAA0B,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,sBAAsB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAC7G,IAAM,gBAAgB;AACtB,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;;;AF2DhC,IAAM,YAAY;AAEX,IAAM,sBAAN,MAAmE;AAAA,EACxE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAIA,YAAY,UAAoD;AAC9D,SAAK,WAAW;AAAA,MACd,GAAI,eAAe,QAAQ,IAAI,WAAW,EAAE,KAAK,SAAS;AAAA,MAC1D,yBAAyB;AAAA,IAC3B;AAEA,SAAK,gBAAgB,KAAK,gBAA0C,gBAAgB;AACpF,SAAK,kBAAkB,KAAK,gBAA4C,kBAAkB;AAC1F,SAAK,eAAe,KAAK,gBAAyC,eAAe;AACjF,SAAK,cAAc,KAAK,gBAA0C,aAAa;AAC/E,SAAK,WAAW,KAAK,gBAA+C,iBAAiB;AACrF,SAAK,UAAU,KAAK,gBAA8C,gBAAgB;AAAA,EACpF;AAAA,EAEA,YAAY,MAAiB,UAAmB,YAAqB;AACnE,QAAI,YAAY,YAAY;AAC1B,aAAO,KAAK,aAAa,IAAI;AAAA,IAC/B,WAAW,YAAY;AACrB,aAAO,KAAK,gBAAgB,IAAI;AAAA,IAClC,OAAO;AACL,aAAO,KAAK,cAAc,IAAI;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,OAAO,YAAY,OAA4D;AAC7E,QAAID,SAAQ,MAAM,KAAK,GAAG;AACxB,YAAM,IAAI,MAAM,oDAAoD;AAAA,IACtE;AAEA,UAAM,gBAAgB,CAAC,QAAa,CAAC,IAAI;AACzC,UAAM,iBAAiB,oBAAI,IAAY;AACvC,UAAM,EAAE,OAAO,KAAK,IAAI;AACxB,UAAM,OAAO,qBAAqB,KAAK;AAEvC,QAAI,iBAAiB,SAAS;AAC9B,QAAI,mBAAmB,SAAS;AAChC,UAAM,iBAAiB;AAEvB,OAAG;AACD,YAAM,WAAW,MAAM,KAAK,YAAY,MAAM,gBAAgB,gBAAgB;AAE9E,YAAM,EAAE,KAAK,IAAI;AACjB,UAAI,QAA4B,CAAC;AAEjC,UAAI,kBAAkB,aAAa,IAAI,GAAG;AACxC,YAAIC,MAAK,KAAK,KAAK,GAAG;AACpB,gBAAM,kBACJ,iBAAiB,KAAK,MAAM,OAAO,aAAa,IAAI,KAAK,OACzD,IAAI,YAAY,IAAI,CAAC;AAEvB,kBAAQ,MAAM,OAAO,cAAc;AAAA,QACrC;AAEA,yBAAiB,KAAK,MAAM,WAAW;AAAA,MACzC;AAEA,UAAI,kBAAkB,WAAW,IAAI,GAAG;AACtC,YAAIA,MAAK,KAAK,QAAQ,KAAK,GAAG;AAC5B,gBAAM,eAAe,KAAK,QAAQ,MAAM,OAAO,aAAa,EAAE,IAAI,YAAY,KAAK,CAAC;AACpF,kBAAQ,MAAM,OAAO,YAAY;AAAA,QACnC;AAEA,2BAAmB,KAAK,QAAQ,MAAM,WAAW;AAAA,MACnD;AAEA,UAAIA,MAAK,KAAK,GAAG;AAGf,YAAI,MAAM,KAAK,CAAC,QAAQ,eAAe,IAAI,IAAI,KAAK,CAAC,GAAG;AACtD,kBAAQ,MAAM,OAAO,CAAC,MAAM,CAAC,eAAe,IAAI,EAAE,KAAK,CAAC;AAAA,QAC1D;AAEA,YAAIA,MAAK,KAAK,GAAG;AACf,kBAAQ,OAAO,OAAO,CAAC,QAAQ,IAAI,KAAK;AACxC,gBAAM,QAAQ,CAAC,QAAQ,eAAe,IAAI,IAAI,KAAK,CAAC;AAEpD,gBAAM;AAAA,QACR;AAAA,MACF;AAEA,UAAI,kBAAkB;AAAkB,aAAK,QAAQ;AAAA,IACvD,SAAS,kBAAkB;AAAA,EAC7B;AAAA,EAEA,MAAM,SAAS,OAAqD;AAClE,QAAI,QAA4B,CAAC;AACjC,qBAAiB,SAAS,KAAK,YAAY,KAAK,GAAG;AACjD,cAAQ,MAAM,OAAO,KAAK;AAAA,IAC5B;AAEA,WAAO,QAAQ,OAAO,CAAC,QAAQ,IAAI,cAAc;AAAA,EACnD;AAAA,EAEA,MAAM,WAAW,OAA4C;AAC3D,UAAM,WAAW,MAAM,KAAK,YAAY,KAAK;AAE7C,WACE,SAAS,MAAM,aAAa,IAAI,CAAC,YAAY;AAAA,MAC3C,GAAG;AAAA,MACH,IAAI,OAAO;AAAA,MACX,WAAW,OAAO,OAAO,SAAS;AAAA,MAClC,OAAO,OAAO,OAAO,KAAK;AAAA,MAC1B,OAAO,OAAO,MAAM,KAAK,EAAE;AAAA,IAC7B,EAAE,KAAK,CAAC;AAAA,EAEZ;AAAA,EAEA,gBACE,OACA,SACgD;AAChD,UAAM,MAAMF,gBAAe,SAAS,KAAK,QAAQ;AACjD,QAAI,0BAA0B;AAE9B,WAAO,mBAAmB,OAAO,GAAG;AAAA,EACtC;AAAA,EAEA,MAAM,iBACJ,mBACsC;AACtC,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,SAAS,EAAE,kBAAkB,CAAC;AAE1D,aAAO,EAAE,SAAS,MAAM,eAAe,SAAS,KAAK,iBAAiB;AAAA,IACxE,SAAS,GAAG;AACV,aAAO,EAAE,SAAS,OAAO,SAAU,EAAY,QAAQ;AAAA,IACzD;AAAA,EACF;AAAA,EAEA,MAAM,kBACJ,mBACsC;AACtC,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,QAAQ,EAAE,kBAAkB,CAAC;AAEzD,aAAO,EAAE,SAAS,MAAM,eAAe,SAAS,KAAK,kBAAkB;AAAA,IACzE,SAAS,GAAG;AACV,aAAO,EAAE,SAAS,OAAO,SAAU,EAAY,QAAQ;AAAA,IACzD;AAAA,EACF;AAAA,EAEA,oBAAyD;AACvD,UAAM,IAAI,kBAAkB,wDAAwD;AAAA,EACtF;AACF;AAEA,SAAS,qBAAqB,OAAwB;AACpD,QAAM,OAAO;AAAA,IACX,OAAO;AAAA,IACP,QAAQ,MAAM,MAAM,QAAQ,MAAM,KAAK;AAAA,IACvC,WAAW,MAAM,MAAM,WAAW,MAAM,QAAQ;AAAA,IAChD,sBAAsB,MAAM;AAAA,IAC5B,SAAS,MAAM;AAAA,IACf,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAEA,QAAM,YAAY,MAAM,MAAM,WAAW,MAAM,OAAO;AACtD,MAAIE,MAAK,SAAS,GAAG;AACnB,UAAM,QAAQ,UAAU;AAAA,MAAI,CAAC,YAC3B,OAAO,YAAY,WAAW,YAAY,WAAW,OAAO,EAAE,WAAW,QAAQ;AAAA,IACnF;AAEA,SAAK,YAAY,KAAKA,MAAK,KAAK,SAAS,IAAI,KAAK,UAAU,OAAO,KAAK,IAAI,KAAK;AAAA,EACnF;AAEA,SAAO;AACT;AAEA,SAAS,MAAS,OAAa,IAAQ;AACrC,MAAID,SAAQ,KAAK,KAAK,YAAY,EAAE;AAAG;AAEvC,QAAM,MAAM,IAAI,IAAO,SAAS,CAAC,CAAC;AAClC,MAAI,CAAC,YAAY,EAAE;AAAG,QAAI,IAAI,EAAE;AAChC,SAAO,MAAM,KAAK,IAAI,OAAO,CAAC;AAChC;AAEA,SAAS,WAAW,MAA+E;AACjG,SAAO,CAAC,CAAE,MAA0B,SAAS;AAC/C;AAEA,SAAS,aAAa,MAA6E;AACjG,SAAO,CAAC,CAAE,MAAwB;AACpC;AAEA,SAAS,YAAY,WAAoB;AACvC,SAAO,CAAC,SAAgC;AAAA,IACtC,GAAG;AAAA,IACH,OAAO,OAAO,IAAI,KAAK;AAAA,IACvB,QAAQ,IAAI,OAAO,IAAI,CAAC,WAAW;AAAA,MACjC,SAAS,MAAM;AAAA,MACf,QAAQ,OAAO,MAAM,MAAM;AAAA,IAC7B,EAAE;AAAA,IACF;AAAA,EACF;AACF","sourcesContent":["import {\n Box,\n QueryBoxesArgs as BoxesArgs,\n Header,\n QueryBlockHeadersArgs as HeadersArgs\n} from \"@ergo-graphql/types\";\nimport {\n Base58String,\n BlockHeader,\n ensureDefaults,\n HexString,\n isEmpty,\n isUndefined,\n NotSupportedError,\n orderBy,\n SignedTransaction,\n some,\n uniq,\n uniqBy\n} from \"@fleet-sdk/common\";\nimport { ErgoAddress } from \"@fleet-sdk/core\";\nimport {\n BoxQuery,\n BoxWhere,\n ChainProviderBox,\n HeaderQuery,\n IBlockchainProvider,\n TransactionEvaluationResult,\n TransactionReductionResult\n} from \"../types\";\nimport {\n createGqlOperation,\n GraphQLOperation,\n GraphQLRequestOptions,\n GraphQLSuccessResponse,\n GraphQLThrowableOptions,\n GraphQLVariables,\n isRequestParam\n} from \"../utils\";\nimport {\n ALL_BOXES_QUERY,\n CHECK_TX_MUTATION,\n CONF_BOXES_QUERY,\n HEADERS_QUERY,\n SEND_TX_MUTATION,\n UNCONF_BOXES_QUERY\n} from \"./queries\";\n\nexport type GraphQLBoxWhere = BoxWhere & {\n /** Base16-encoded BoxIds */\n boxIds?: HexString[];\n\n /** Base16-encoded ErgoTrees */\n ergoTrees?: HexString[];\n\n /** Base58-encoded addresses or `ErgoAddress` objects */\n addresses?: (Base58String | ErgoAddress)[];\n};\n\nexport type GraphQLBoxQuery = BoxQuery<GraphQLBoxWhere>;\nexport type ErgoGraphQLRequestOptions = Omit<GraphQLRequestOptions, \"throwOnNonNetworkError\">;\n\ntype ConfBoxesResp = { boxes: Box[] };\ntype UnconfBoxesResp = { mempool: { boxes: Box[] } };\ntype AllBoxesResp = ConfBoxesResp & UnconfBoxesResp;\ntype HeadersResp = { blockHeaders: Header[] };\ntype CheckTxResp = { checkTransaction: string };\ntype SendTxResp = { submitTransaction: string };\ntype SignedTxArgsResp = { signedTransaction: SignedTransaction };\n\nconst PAGE_SIZE = 50;\n\nexport class ErgoGraphQLProvider implements IBlockchainProvider<BoxWhere> {\n #options: GraphQLThrowableOptions;\n\n #getConfBoxes;\n #getUnconfBoxes;\n #getAllBoxes;\n #getHeaders;\n #checkTx;\n #sendTx;\n\n constructor(url: string | URL);\n constructor(url: ErgoGraphQLRequestOptions);\n constructor(optOrUrl: ErgoGraphQLRequestOptions | string | URL) {\n this.#options = {\n ...(isRequestParam(optOrUrl) ? optOrUrl : { url: optOrUrl }),\n throwOnNonNetworkErrors: true\n };\n\n this.#getConfBoxes = this.createOperation<ConfBoxesResp, BoxesArgs>(CONF_BOXES_QUERY);\n this.#getUnconfBoxes = this.createOperation<UnconfBoxesResp, BoxesArgs>(UNCONF_BOXES_QUERY);\n this.#getAllBoxes = this.createOperation<AllBoxesResp, BoxesArgs>(ALL_BOXES_QUERY);\n this.#getHeaders = this.createOperation<HeadersResp, HeadersArgs>(HEADERS_QUERY);\n this.#checkTx = this.createOperation<CheckTxResp, SignedTxArgsResp>(CHECK_TX_MUTATION);\n this.#sendTx = this.createOperation<SendTxResp, SignedTxArgsResp>(SEND_TX_MUTATION);\n }\n\n #fetchBoxes(args: BoxesArgs, inclConf: boolean, inclUnconf: boolean) {\n if (inclConf && inclUnconf) {\n return this.#getAllBoxes(args);\n } else if (inclUnconf) {\n return this.#getUnconfBoxes(args);\n } else {\n return this.#getConfBoxes(args);\n }\n }\n\n async *streamBoxes(query: GraphQLBoxQuery): AsyncGenerator<ChainProviderBox[]> {\n if (isEmpty(query.where)) {\n throw new Error(\"Cannot fetch unspent boxes without a where clause.\");\n }\n\n const notBeingSpent = (box: Box) => !box.beingSpent;\n const returnedBoxIds = new Set<string>();\n const { where, from } = query;\n const args = buildGqlBoxQueryArgs(where);\n\n let fetchFromChain = from !== \"mempool\";\n let fetchFromMempool = from !== \"blockchain\";\n const isMempoolAware = fetchFromMempool;\n\n do {\n const response = await this.#fetchBoxes(args, fetchFromChain, fetchFromMempool);\n\n const { data } = response;\n let boxes: ChainProviderBox[] = [];\n\n if (fetchFromChain && hasConfirmed(data)) {\n if (some(data.boxes)) {\n const confirmedBoxes = (\n isMempoolAware ? data.boxes.filter(notBeingSpent) : data.boxes\n ).map(asConfirmed(true));\n\n boxes = boxes.concat(confirmedBoxes);\n }\n\n fetchFromChain = data.boxes.length === PAGE_SIZE;\n }\n\n if (isMempoolAware && hasMempool(data)) {\n if (some(data.mempool.boxes)) {\n const mempoolBoxes = data.mempool.boxes.filter(notBeingSpent).map(asConfirmed(false));\n boxes = boxes.concat(mempoolBoxes);\n }\n\n fetchFromMempool = data.mempool.boxes.length === PAGE_SIZE;\n }\n\n if (some(boxes)) {\n // boxes can be moved from the mempool to the blockchain while streaming,\n // so we need to filter out boxes that have already been returned.\n if (boxes.some((box) => returnedBoxIds.has(box.boxId))) {\n boxes = boxes.filter((b) => !returnedBoxIds.has(b.boxId));\n }\n\n if (some(boxes)) {\n boxes = uniqBy(boxes, (box) => box.boxId);\n boxes.forEach((box) => returnedBoxIds.add(box.boxId));\n\n yield boxes;\n }\n }\n\n if (fetchFromChain || fetchFromMempool) args.skip += PAGE_SIZE;\n } while (fetchFromChain || fetchFromMempool);\n }\n\n async getBoxes(query: GraphQLBoxQuery): Promise<ChainProviderBox[]> {\n let boxes: ChainProviderBox[] = [];\n for await (const chunk of this.streamBoxes(query)) {\n boxes = boxes.concat(chunk);\n }\n\n return orderBy(boxes, (box) => box.creationHeight);\n }\n\n async getHeaders(query: HeaderQuery): Promise<BlockHeader[]> {\n const response = await this.#getHeaders(query);\n\n return (\n response.data?.blockHeaders.map((header) => ({\n ...header,\n id: header.headerId,\n timestamp: Number(header.timestamp),\n nBits: Number(header.nBits),\n votes: header.votes.join(\"\")\n })) ?? []\n );\n }\n\n createOperation<R, V extends GraphQLVariables = GraphQLVariables>(\n query: string,\n options?: Partial<ErgoGraphQLRequestOptions>\n ): GraphQLOperation<GraphQLSuccessResponse<R>, V> {\n const opt = ensureDefaults(options, this.#options);\n opt.throwOnNonNetworkErrors = true;\n\n return createGqlOperation(query, opt);\n }\n\n async checkTransaction(\n signedTransaction: SignedTransaction\n ): Promise<TransactionEvaluationResult> {\n try {\n const response = await this.#checkTx({ signedTransaction });\n\n return { success: true, transactionId: response.data.checkTransaction };\n } catch (e) {\n return { success: false, message: (e as Error).message };\n }\n }\n\n async submitTransaction(\n signedTransaction: SignedTransaction\n ): Promise<TransactionEvaluationResult> {\n try {\n const response = await this.#sendTx({ signedTransaction });\n\n return { success: true, transactionId: response.data.submitTransaction };\n } catch (e) {\n return { success: false, message: (e as Error).message };\n }\n }\n\n reduceTransaction(): Promise<TransactionReductionResult> {\n throw new NotSupportedError(\"Transaction reducing is not supported by ergo-graphql.\");\n }\n}\n\nfunction buildGqlBoxQueryArgs(where: GraphQLBoxWhere) {\n const args = {\n spent: false,\n boxIds: merge(where.boxIds, where.boxId),\n ergoTrees: merge(where.ergoTrees, where.ergoTree),\n ergoTreeTemplateHash: where.templateHash,\n tokenId: where.tokenId,\n skip: 0,\n take: PAGE_SIZE\n } satisfies BoxesArgs;\n\n const addresses = merge(where.addresses, where.address);\n if (some(addresses)) {\n const trees = addresses.map((address) =>\n typeof address === \"string\" ? ErgoAddress.fromBase58(address).ergoTree : address.ergoTree\n );\n\n args.ergoTrees = uniq(some(args.ergoTrees) ? args.ergoTrees.concat(trees) : trees);\n }\n\n return args;\n}\n\nfunction merge<T>(array?: T[], el?: T) {\n if (isEmpty(array) && isUndefined(el)) return;\n\n const set = new Set<T>(array ?? []);\n if (!isUndefined(el)) set.add(el);\n return Array.from(set.values());\n}\n\nfunction hasMempool(data: AllBoxesResp | ConfBoxesResp | UnconfBoxesResp): data is UnconfBoxesResp {\n return !!(data as UnconfBoxesResp)?.mempool?.boxes;\n}\n\nfunction hasConfirmed(data: AllBoxesResp | ConfBoxesResp | UnconfBoxesResp): data is ConfBoxesResp {\n return !!(data as ConfBoxesResp)?.boxes;\n}\n\nfunction asConfirmed(confirmed: boolean) {\n return (box: Box): ChainProviderBox => ({\n ...box,\n value: BigInt(box.value),\n assets: box.assets.map((asset) => ({\n tokenId: asset.tokenId,\n amount: BigInt(asset.amount)\n })),\n confirmed\n });\n}\n","import {\n BlockchainProviderError,\n clearUndefined,\n ensureDefaults,\n isEmpty,\n some\n} from \"@fleet-sdk/common\";\n\nconst OP_NAME_REGEX = /(query|mutation)\\s?([\\w\\-_]+)?/;\nexport const DEFAULT_HEADERS: Headers = {\n \"content-type\": \"application/json; charset=utf-8\",\n accept: \"application/graphql-response+json, application/json\"\n};\n\ntype Credentials = RequestCredentials;\ntype Headers = HeadersInit;\ntype Fetcher = typeof fetch;\n\nexport type GraphQLVariables = Record<string, unknown> | null;\n\nexport interface GraphQLError {\n message: string;\n}\n\nexport interface GraphQLSuccessResponse<T = unknown> {\n data: T;\n errors: null;\n}\n\nexport interface GraphQLErrorResponse {\n data: null;\n errors: GraphQLError[];\n}\n\nexport type GraphQLResponse<T = unknown> = GraphQLSuccessResponse<T> | GraphQLErrorResponse;\n\nexport type GraphQLOperation<R extends GraphQLResponse, V extends GraphQLVariables> = (\n variables?: V\n) => Promise<R>;\n\nexport interface ResponseParser {\n parse<T>(text: string): T;\n stringify<T>(value: T): string;\n}\n\nexport interface RequestParams {\n operationName?: string | null;\n query: string;\n variables?: Record<string, unknown> | null;\n}\n\nexport interface GraphQLRequestOptions {\n url: URL | string;\n headers?: Headers;\n parser?: ResponseParser;\n fetcher?: Fetcher;\n credentials?: Credentials;\n throwOnNonNetworkErrors?: boolean;\n}\n\nexport interface GraphQLThrowableOptions extends GraphQLRequestOptions {\n throwOnNonNetworkErrors: true;\n}\n\nexport function createGqlOperation<R, V extends GraphQLVariables = GraphQLVariables>(\n query: string,\n options: GraphQLThrowableOptions\n): GraphQLOperation<GraphQLSuccessResponse<R>, V>;\nexport function createGqlOperation<R, V extends GraphQLVariables = GraphQLVariables>(\n query: string,\n options: GraphQLRequestOptions\n): GraphQLOperation<GraphQLResponse<R>, V>;\nexport function createGqlOperation<R, V extends GraphQLVariables = GraphQLVariables>(\n query: string,\n options: GraphQLRequestOptions\n): GraphQLOperation<GraphQLResponse<R>, V> {\n return async (variables?: V): Promise<GraphQLResponse<R>> => {\n const response = await (options.fetcher ?? fetch)(options.url, {\n method: \"POST\",\n headers: ensureDefaults(options.headers, DEFAULT_HEADERS),\n credentials: options.credentials,\n body: (options.parser ?? JSON).stringify({\n operationName: getOpName(query),\n query,\n variables: variables ? clearUndefined(variables) : undefined\n } as RequestParams)\n });\n\n const rawData = await response.text();\n const parsedData = (options.parser ?? JSON).parse(rawData) as GraphQLResponse<R>;\n\n if (options.throwOnNonNetworkErrors && some(parsedData.errors) && isEmpty(parsedData.data)) {\n throw new BlockchainProviderError(parsedData.errors[0].message, { cause: parsedData.errors });\n }\n\n return parsedData;\n };\n}\n\nexport function gql(query: TemplateStringsArray): string {\n return query[0];\n}\n\nexport function getOpName(query: string): string | undefined {\n return OP_NAME_REGEX.exec(query)?.at(2);\n}\n\nexport function isRequestParam(obj: unknown): obj is GraphQLRequestOptions {\n return typeof obj === \"object\" && (obj as GraphQLRequestOptions).url !== undefined;\n}\n","const B = [\n `query boxes($spent: Boolean! $boxIds: [String!] $ergoTrees: [String!] $ergoTreeTemplateHash: String $tokenId: String $skip: Int $take: Int)`,\n `boxIds: $boxIds ergoTrees: $ergoTrees ergoTreeTemplateHash: $ergoTreeTemplateHash tokenId: $tokenId skip: $skip take: $take`,\n `boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters beingSpent`\n];\n\nexport const CONF_BOXES_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } }`;\nexport const UNCONF_BOXES_QUERY = `${B[0]} { mempool { boxes(${B[1]}) { ${B[2]} } } }`;\nexport const ALL_BOXES_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } mempool { boxes(${B[1]}) { ${B[2]} } } }`;\nexport const HEADERS_QUERY = `query blockHeaders($take: Int) { blockHeaders(take: $take) {headerId timestamp version adProofsRoot stateRoot transactionsRoot nBits extensionHash powSolutions height difficulty parentId votes } }`;\nexport const CHECK_TX_MUTATION = `mutation checkTransaction($signedTransaction: SignedTransaction!) { checkTransaction(signedTransaction: $signedTransaction) }`;\nexport const SEND_TX_MUTATION = `mutation submitTransaction($signedTransaction: SignedTransaction!) { submitTransaction(signedTransaction: $signedTransaction) }`;\n"]}
|
package/dist/index.d.mts
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
import { BoxId, HexString, Base58String, TokenId, Box, BlockHeader, SignedTransaction, UnsignedTransaction, TransactionId } from '@fleet-sdk/common';
|
2
|
+
import { ErgoAddress } from '@fleet-sdk/core';
|
3
|
+
|
4
|
+
declare global {
|
5
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- It has to be an `interface` so that it can be merged.
|
6
|
+
interface SymbolConstructor {
|
7
|
+
readonly observable: symbol;
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
/**
|
12
|
+
Returns a boolean for whether the two given types are equal.
|
13
|
+
|
14
|
+
@link https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650
|
15
|
+
@link https://stackoverflow.com/questions/68961864/how-does-the-equals-work-in-typescript/68963796#68963796
|
16
|
+
|
17
|
+
Use-cases:
|
18
|
+
- If you want to make a conditional branch based on the result of a comparison of two types.
|
19
|
+
|
20
|
+
@example
|
21
|
+
```
|
22
|
+
import type {IsEqual} from 'type-fest';
|
23
|
+
|
24
|
+
// This type returns a boolean for whether the given array includes the given item.
|
25
|
+
// `IsEqual` is used to compare the given array at position 0 and the given item and then return true if they are equal.
|
26
|
+
type Includes<Value extends readonly any[], Item> =
|
27
|
+
Value extends readonly [Value[0], ...infer rest]
|
28
|
+
? IsEqual<Value[0], Item> extends true
|
29
|
+
? true
|
30
|
+
: Includes<rest, Item>
|
31
|
+
: false;
|
32
|
+
```
|
33
|
+
|
34
|
+
@category Type Guard
|
35
|
+
@category Utilities
|
36
|
+
*/
|
37
|
+
type IsEqual<A, B> =
|
38
|
+
(<G>() => G extends A ? 1 : 2) extends
|
39
|
+
(<G>() => G extends B ? 1 : 2)
|
40
|
+
? true
|
41
|
+
: false;
|
42
|
+
|
43
|
+
/**
|
44
|
+
Filter out keys from an object.
|
45
|
+
|
46
|
+
Returns `never` if `Exclude` is strictly equal to `Key`.
|
47
|
+
Returns `never` if `Key` extends `Exclude`.
|
48
|
+
Returns `Key` otherwise.
|
49
|
+
|
50
|
+
@example
|
51
|
+
```
|
52
|
+
type Filtered = Filter<'foo', 'foo'>;
|
53
|
+
//=> never
|
54
|
+
```
|
55
|
+
|
56
|
+
@example
|
57
|
+
```
|
58
|
+
type Filtered = Filter<'bar', string>;
|
59
|
+
//=> never
|
60
|
+
```
|
61
|
+
|
62
|
+
@example
|
63
|
+
```
|
64
|
+
type Filtered = Filter<'bar', 'foo'>;
|
65
|
+
//=> 'bar'
|
66
|
+
```
|
67
|
+
|
68
|
+
@see {Except}
|
69
|
+
*/
|
70
|
+
type Filter<KeyType, ExcludeType> = IsEqual<KeyType, ExcludeType> extends true ? never : (KeyType extends ExcludeType ? never : KeyType);
|
71
|
+
|
72
|
+
type ExceptOptions = {
|
73
|
+
/**
|
74
|
+
Disallow assigning non-specified properties.
|
75
|
+
|
76
|
+
Note that any omitted properties in the resulting type will be present in autocomplete as `undefined`.
|
77
|
+
|
78
|
+
@default false
|
79
|
+
*/
|
80
|
+
requireExactProps?: boolean;
|
81
|
+
};
|
82
|
+
|
83
|
+
/**
|
84
|
+
Create a type from an object type without certain keys.
|
85
|
+
|
86
|
+
We recommend setting the `requireExactProps` option to `true`.
|
87
|
+
|
88
|
+
This type is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type). The `Omit` type does not restrict the omitted keys to be keys present on the given type, while `Except` does. The benefits of a stricter type are avoiding typos and allowing the compiler to pick up on rename refactors automatically.
|
89
|
+
|
90
|
+
This type was proposed to the TypeScript team, which declined it, saying they prefer that libraries implement stricter versions of the built-in types ([microsoft/TypeScript#30825](https://github.com/microsoft/TypeScript/issues/30825#issuecomment-523668235)).
|
91
|
+
|
92
|
+
@example
|
93
|
+
```
|
94
|
+
import type {Except} from 'type-fest';
|
95
|
+
|
96
|
+
type Foo = {
|
97
|
+
a: number;
|
98
|
+
b: string;
|
99
|
+
};
|
100
|
+
|
101
|
+
type FooWithoutA = Except<Foo, 'a'>;
|
102
|
+
//=> {b: string}
|
103
|
+
|
104
|
+
const fooWithoutA: FooWithoutA = {a: 1, b: '2'};
|
105
|
+
//=> errors: 'a' does not exist in type '{ b: string; }'
|
106
|
+
|
107
|
+
type FooWithoutB = Except<Foo, 'b', {requireExactProps: true}>;
|
108
|
+
//=> {a: number} & Partial<Record<"b", never>>
|
109
|
+
|
110
|
+
const fooWithoutB: FooWithoutB = {a: 1, b: '2'};
|
111
|
+
//=> errors at 'b': Type 'string' is not assignable to type 'undefined'.
|
112
|
+
```
|
113
|
+
|
114
|
+
@category Object
|
115
|
+
*/
|
116
|
+
type Except<ObjectType, KeysType extends keyof ObjectType, Options extends ExceptOptions = {requireExactProps: false}> = {
|
117
|
+
[KeyType in keyof ObjectType as Filter<KeyType, KeysType>]: ObjectType[KeyType];
|
118
|
+
} & (Options['requireExactProps'] extends true
|
119
|
+
? Partial<Record<KeysType, never>>
|
120
|
+
: {});
|
121
|
+
|
122
|
+
/**
|
123
|
+
Create a type that requires at least one of the given keys. The remaining keys are kept as is.
|
124
|
+
|
125
|
+
@example
|
126
|
+
```
|
127
|
+
import type {RequireAtLeastOne} from 'type-fest';
|
128
|
+
|
129
|
+
type Responder = {
|
130
|
+
text?: () => string;
|
131
|
+
json?: () => string;
|
132
|
+
secure?: boolean;
|
133
|
+
};
|
134
|
+
|
135
|
+
const responder: RequireAtLeastOne<Responder, 'text' | 'json'> = {
|
136
|
+
json: () => '{"message": "ok"}',
|
137
|
+
secure: true
|
138
|
+
};
|
139
|
+
```
|
140
|
+
|
141
|
+
@category Object
|
142
|
+
*/
|
143
|
+
type RequireAtLeastOne<
|
144
|
+
ObjectType,
|
145
|
+
KeysType extends keyof ObjectType = keyof ObjectType,
|
146
|
+
> = {
|
147
|
+
// For each `Key` in `KeysType` make a mapped type:
|
148
|
+
[Key in KeysType]-?: Required<Pick<ObjectType, Key>> & // 1. Make `Key`'s type required
|
149
|
+
// 2. Make all other keys in `KeysType` optional
|
150
|
+
Partial<Pick<ObjectType, Exclude<KeysType, Key>>>;
|
151
|
+
}[KeysType] &
|
152
|
+
// 3. Add the remaining keys not in `KeysType`
|
153
|
+
Except<ObjectType, KeysType>;
|
154
|
+
|
155
|
+
type BoxSource = "blockchain" | "mempool" | "blockchain+mempool";
|
156
|
+
type BoxQuery<W extends BoxWhere> = {
|
157
|
+
/** The query to filter boxes. */
|
158
|
+
where: RequireAtLeastOne<W>;
|
159
|
+
/**
|
160
|
+
* The source of boxes to query.
|
161
|
+
* @default "blockchain+mempool"
|
162
|
+
*/
|
163
|
+
from?: BoxSource;
|
164
|
+
};
|
165
|
+
type HeaderQuery = {
|
166
|
+
take: number;
|
167
|
+
};
|
168
|
+
type BoxWhere = {
|
169
|
+
/** Base16-encoded BoxId */
|
170
|
+
boxId?: BoxId;
|
171
|
+
/** Base16-encoded ErgoTree */
|
172
|
+
ergoTree?: HexString;
|
173
|
+
/** Base58-encoded address */
|
174
|
+
address?: ErgoAddress | Base58String;
|
175
|
+
/** Base16-encoded contract template hash */
|
176
|
+
templateHash?: HexString;
|
177
|
+
/** Base16-encoded TokenId */
|
178
|
+
tokenId?: TokenId;
|
179
|
+
};
|
180
|
+
type ChainProviderBox = Box<bigint> & {
|
181
|
+
confirmed: boolean;
|
182
|
+
};
|
183
|
+
type TransactionEvaluationError = {
|
184
|
+
success: false;
|
185
|
+
message: string;
|
186
|
+
};
|
187
|
+
type TransactionEvaluationSuccess = {
|
188
|
+
success: true;
|
189
|
+
transactionId: TransactionId;
|
190
|
+
};
|
191
|
+
type TransactionReductionSuccess = {
|
192
|
+
success: true;
|
193
|
+
reducedTransaction: HexString;
|
194
|
+
};
|
195
|
+
type TransactionEvaluationResult = TransactionEvaluationError | TransactionEvaluationSuccess;
|
196
|
+
type TransactionReductionResult = TransactionEvaluationError | TransactionReductionSuccess;
|
197
|
+
/**
|
198
|
+
* Represents a blockchain provider that can interact with the blockchain.
|
199
|
+
* @template B The type of the box query used by the provider.
|
200
|
+
*/
|
201
|
+
interface IBlockchainProvider<B extends BoxWhere> {
|
202
|
+
/**
|
203
|
+
* Get boxes.
|
204
|
+
*/
|
205
|
+
getBoxes(query: BoxQuery<B>): Promise<ChainProviderBox[]>;
|
206
|
+
/**
|
207
|
+
* Stream boxes.
|
208
|
+
*/
|
209
|
+
streamBoxes(query: BoxQuery<B>): AsyncIterable<ChainProviderBox[]>;
|
210
|
+
/**
|
211
|
+
* Get headers.
|
212
|
+
*/
|
213
|
+
getHeaders(query: HeaderQuery): Promise<BlockHeader[]>;
|
214
|
+
/**
|
215
|
+
* Check for transaction validity without broadcasting it to the network.
|
216
|
+
*/
|
217
|
+
checkTransaction(transaction: SignedTransaction): Promise<TransactionEvaluationResult>;
|
218
|
+
/**
|
219
|
+
* Broadcast a transaction to the network.
|
220
|
+
*/
|
221
|
+
submitTransaction(transaction: SignedTransaction): Promise<TransactionEvaluationResult>;
|
222
|
+
/**
|
223
|
+
* Evaluate a transaction and return Base16-encoded evaluation result.
|
224
|
+
*/
|
225
|
+
reduceTransaction(transaction: UnsignedTransaction): Promise<TransactionReductionResult>;
|
226
|
+
}
|
227
|
+
|
228
|
+
type Credentials = RequestCredentials;
|
229
|
+
type Headers = HeadersInit;
|
230
|
+
type Fetcher = typeof fetch;
|
231
|
+
type GraphQLVariables = Record<string, unknown> | null;
|
232
|
+
interface GraphQLError {
|
233
|
+
message: string;
|
234
|
+
}
|
235
|
+
interface GraphQLSuccessResponse<T = unknown> {
|
236
|
+
data: T;
|
237
|
+
errors: null;
|
238
|
+
}
|
239
|
+
interface GraphQLErrorResponse {
|
240
|
+
data: null;
|
241
|
+
errors: GraphQLError[];
|
242
|
+
}
|
243
|
+
type GraphQLResponse<T = unknown> = GraphQLSuccessResponse<T> | GraphQLErrorResponse;
|
244
|
+
type GraphQLOperation<R extends GraphQLResponse, V extends GraphQLVariables> = (variables?: V) => Promise<R>;
|
245
|
+
interface ResponseParser {
|
246
|
+
parse<T>(text: string): T;
|
247
|
+
stringify<T>(value: T): string;
|
248
|
+
}
|
249
|
+
interface GraphQLRequestOptions {
|
250
|
+
url: URL | string;
|
251
|
+
headers?: Headers;
|
252
|
+
parser?: ResponseParser;
|
253
|
+
fetcher?: Fetcher;
|
254
|
+
credentials?: Credentials;
|
255
|
+
throwOnNonNetworkErrors?: boolean;
|
256
|
+
}
|
257
|
+
|
258
|
+
type GraphQLBoxWhere = BoxWhere & {
|
259
|
+
/** Base16-encoded BoxIds */
|
260
|
+
boxIds?: HexString[];
|
261
|
+
/** Base16-encoded ErgoTrees */
|
262
|
+
ergoTrees?: HexString[];
|
263
|
+
/** Base58-encoded addresses or `ErgoAddress` objects */
|
264
|
+
addresses?: (Base58String | ErgoAddress)[];
|
265
|
+
};
|
266
|
+
type GraphQLBoxQuery = BoxQuery<GraphQLBoxWhere>;
|
267
|
+
type ErgoGraphQLRequestOptions = Omit<GraphQLRequestOptions, "throwOnNonNetworkError">;
|
268
|
+
declare class ErgoGraphQLProvider implements IBlockchainProvider<BoxWhere> {
|
269
|
+
#private;
|
270
|
+
constructor(url: string | URL);
|
271
|
+
constructor(url: ErgoGraphQLRequestOptions);
|
272
|
+
streamBoxes(query: GraphQLBoxQuery): AsyncGenerator<ChainProviderBox[]>;
|
273
|
+
getBoxes(query: GraphQLBoxQuery): Promise<ChainProviderBox[]>;
|
274
|
+
getHeaders(query: HeaderQuery): Promise<BlockHeader[]>;
|
275
|
+
createOperation<R, V extends GraphQLVariables = GraphQLVariables>(query: string, options?: Partial<ErgoGraphQLRequestOptions>): GraphQLOperation<GraphQLSuccessResponse<R>, V>;
|
276
|
+
checkTransaction(signedTransaction: SignedTransaction): Promise<TransactionEvaluationResult>;
|
277
|
+
submitTransaction(signedTransaction: SignedTransaction): Promise<TransactionEvaluationResult>;
|
278
|
+
reduceTransaction(): Promise<TransactionReductionResult>;
|
279
|
+
}
|
280
|
+
|
281
|
+
export { ErgoGraphQLProvider, ErgoGraphQLRequestOptions, GraphQLBoxQuery, GraphQLBoxWhere };
|