@ar.io/sdk 3.21.1-alpha.1 → 3.22.0-alpha.2
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/bundles/web.bundle.min.js +69 -69
- package/lib/cjs/cli/cli.js +1 -5
- package/lib/cjs/cli/options.js +6 -0
- package/lib/cjs/cli/utils.js +42 -1
- package/lib/cjs/common/hyperbeam/hb.js +173 -0
- package/lib/cjs/common/io.js +43 -3
- package/lib/cjs/version.js +1 -1
- package/lib/esm/cli/cli.js +1 -5
- package/lib/esm/cli/options.js +6 -0
- package/lib/esm/cli/utils.js +41 -1
- package/lib/esm/common/hyperbeam/hb.js +169 -0
- package/lib/esm/common/io.js +43 -3
- package/lib/esm/version.js +1 -1
- package/lib/types/cli/options.d.ts +4 -0
- package/lib/types/cli/utils.d.ts +2 -1
- package/lib/types/common/hyperbeam/hb.d.ts +88 -0
- package/lib/types/common/io.d.ts +4 -2
- package/lib/types/types/common.d.ts +3 -0
- package/lib/types/types/io.d.ts +1 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
package/lib/cjs/cli/cli.js
CHANGED
|
@@ -448,11 +448,7 @@ const utils_js_1 = require("./utils.js");
|
|
|
448
448
|
(0, utils_js_1.makeCommand)({
|
|
449
449
|
name: 'get-ants-for-address',
|
|
450
450
|
description: 'Get the list of ANTs owned by an address according to the ANT registry',
|
|
451
|
-
options: [
|
|
452
|
-
options_js_1.optionMap.address,
|
|
453
|
-
options_js_1.optionMap.antRegistryProcessId,
|
|
454
|
-
options_js_1.optionMap.hyperbeamUrl,
|
|
455
|
-
],
|
|
451
|
+
options: [options_js_1.optionMap.address, options_js_1.optionMap.antRegistryProcessId],
|
|
456
452
|
action: readCommands_js_1.listAntsForAddress,
|
|
457
453
|
});
|
|
458
454
|
// # ANTS
|
package/lib/cjs/cli/options.js
CHANGED
|
@@ -140,6 +140,10 @@ exports.optionMap = {
|
|
|
140
140
|
description: 'The allowed delegates for the gateway. By default this is empty, meaning all are allowed delegate stake unless delegating is explicitly disallowed by the gateway',
|
|
141
141
|
type: 'array',
|
|
142
142
|
},
|
|
143
|
+
services: {
|
|
144
|
+
alias: '--services <services>',
|
|
145
|
+
description: 'JSON string of gateway services configuration (e.g., \'{"bundlers":[{"fqdn":"bundler.example.com","port":443,"protocol":"https","path":"/bundler"}]}\')',
|
|
146
|
+
},
|
|
143
147
|
skipConfirmation: {
|
|
144
148
|
alias: '--skip-confirmation',
|
|
145
149
|
description: 'Skip confirmation prompts',
|
|
@@ -335,6 +339,7 @@ exports.globalOptions = [
|
|
|
335
339
|
exports.optionMap.debug,
|
|
336
340
|
exports.optionMap.arioProcessId,
|
|
337
341
|
exports.optionMap.cuUrl,
|
|
342
|
+
exports.optionMap.hyperbeamUrl,
|
|
338
343
|
];
|
|
339
344
|
exports.writeActionOptions = [exports.optionMap.skipConfirmation, exports.optionMap.tags];
|
|
340
345
|
exports.arnsPurchaseOptions = [
|
|
@@ -406,6 +411,7 @@ exports.updateGatewaySettingsOptions = [
|
|
|
406
411
|
exports.optionMap.fqdn,
|
|
407
412
|
exports.optionMap.port,
|
|
408
413
|
exports.optionMap.protocol,
|
|
414
|
+
exports.optionMap.services,
|
|
409
415
|
];
|
|
410
416
|
exports.joinNetworkOptions = [
|
|
411
417
|
...exports.updateGatewaySettingsOptions,
|
package/lib/cjs/cli/utils.js
CHANGED
|
@@ -27,6 +27,7 @@ exports.paginationParamsFromOptions = paginationParamsFromOptions;
|
|
|
27
27
|
exports.epochInputFromOptions = epochInputFromOptions;
|
|
28
28
|
exports.requiredInitiatorFromOptions = requiredInitiatorFromOptions;
|
|
29
29
|
exports.customTagsFromOptions = customTagsFromOptions;
|
|
30
|
+
exports.servicesFromOptions = servicesFromOptions;
|
|
30
31
|
exports.gatewaySettingsFromOptions = gatewaySettingsFromOptions;
|
|
31
32
|
exports.requiredTargetAndQuantityFromOptions = requiredTargetAndQuantityFromOptions;
|
|
32
33
|
exports.redelegateParamsFromOptions = redelegateParamsFromOptions;
|
|
@@ -180,6 +181,7 @@ function aoProcessFromOptions(options) {
|
|
|
180
181
|
function readARIOFromOptions(options) {
|
|
181
182
|
setLoggerIfDebug(options);
|
|
182
183
|
return index_js_1.ARIO.init({
|
|
184
|
+
hyperbeamUrl: options.hyperbeamUrl,
|
|
183
185
|
process: aoProcessFromOptions({
|
|
184
186
|
cuUrl: 'https://cu.ardrive.io', // default to ardrive cu for ARIO process
|
|
185
187
|
...options,
|
|
@@ -226,6 +228,7 @@ function writeARIOFromOptions(options) {
|
|
|
226
228
|
process: aoProcessFromOptions(options),
|
|
227
229
|
signer,
|
|
228
230
|
paymentUrl: options.paymentUrl,
|
|
231
|
+
hyperbeamUrl: options.hyperbeamUrl,
|
|
229
232
|
}),
|
|
230
233
|
signerAddress,
|
|
231
234
|
};
|
|
@@ -333,7 +336,44 @@ function customTagsFromOptions(options) {
|
|
|
333
336
|
tags,
|
|
334
337
|
};
|
|
335
338
|
}
|
|
336
|
-
function
|
|
339
|
+
function servicesFromOptions(services) {
|
|
340
|
+
if (services === undefined || services === null || services === '') {
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const parsed = JSON.parse(services);
|
|
345
|
+
// Validate structure
|
|
346
|
+
if (!parsed.bundlers || !Array.isArray(parsed.bundlers)) {
|
|
347
|
+
throw new Error('Services must have a "bundlers" array');
|
|
348
|
+
}
|
|
349
|
+
if (parsed.bundlers.length > 20) {
|
|
350
|
+
throw new Error('Maximum 20 bundlers allowed');
|
|
351
|
+
}
|
|
352
|
+
// Validate each bundler
|
|
353
|
+
for (const bundler of parsed.bundlers) {
|
|
354
|
+
if (!bundler.fqdn || typeof bundler.fqdn !== 'string') {
|
|
355
|
+
throw new Error('Each bundler must have a valid "fqdn" string');
|
|
356
|
+
}
|
|
357
|
+
if (typeof bundler.port !== 'number' ||
|
|
358
|
+
bundler.port < 0 ||
|
|
359
|
+
bundler.port > 65535) {
|
|
360
|
+
throw new Error('Each bundler must have a valid "port" (0-65535)');
|
|
361
|
+
}
|
|
362
|
+
if (bundler.protocol !== 'https') {
|
|
363
|
+
throw new Error('Each bundler protocol must be "https"');
|
|
364
|
+
}
|
|
365
|
+
if (!bundler.path || typeof bundler.path !== 'string') {
|
|
366
|
+
throw new Error('Each bundler must have a valid "path" string');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return parsed;
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
throw new Error(`Invalid services JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function gatewaySettingsFromOptions(options) {
|
|
376
|
+
const { allowDelegatedStaking, autoStake, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, observerAddress, port, properties, allowedDelegates, services, } = options;
|
|
337
377
|
return {
|
|
338
378
|
observerAddress,
|
|
339
379
|
allowDelegatedStaking,
|
|
@@ -348,6 +388,7 @@ function gatewaySettingsFromOptions({ allowDelegatedStaking, autoStake, delegate
|
|
|
348
388
|
note,
|
|
349
389
|
port: port !== undefined ? +port : undefined,
|
|
350
390
|
properties,
|
|
391
|
+
services: servicesFromOptions(services),
|
|
351
392
|
};
|
|
352
393
|
}
|
|
353
394
|
function requiredTargetAndQuantityFromOptions(options) {
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HB = void 0;
|
|
4
|
+
const logger_js_1 = require("../logger.js");
|
|
5
|
+
class HB {
|
|
6
|
+
url;
|
|
7
|
+
processId;
|
|
8
|
+
isHyperBeamCompatible;
|
|
9
|
+
checkHyperBeamPromise;
|
|
10
|
+
logger;
|
|
11
|
+
hbTimeoutMs;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.url = config.url;
|
|
14
|
+
this.processId = config.processId;
|
|
15
|
+
this.logger = config.logger ?? logger_js_1.Logger.default;
|
|
16
|
+
this.hbTimeoutMs = config.hbTimeoutMs ?? 5000;
|
|
17
|
+
this.isHyperBeamCompatible = undefined;
|
|
18
|
+
this.checkHyperBeamPromise = this.checkHyperBeamCompatibility();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* fetches the meta data for the process
|
|
22
|
+
*
|
|
23
|
+
* @returns The meta data for the process
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
|
|
27
|
+
* const meta = await hyperbeam.meta();
|
|
28
|
+
* console.log(meta);
|
|
29
|
+
*/
|
|
30
|
+
async meta() {
|
|
31
|
+
const url = new URL(`${this.url}/${this.processId}~process@1.0/meta`);
|
|
32
|
+
return this.fetchHyperbeamPath({
|
|
33
|
+
path: url.toString(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* calls the process device /now function, which evaluates the current process state pulling new messages
|
|
38
|
+
* to get the latest state
|
|
39
|
+
*
|
|
40
|
+
* @param path - The path to the hb state
|
|
41
|
+
* @param json - Whether to return the result as JSON, defaults to true
|
|
42
|
+
* @returns The result of the compute operation
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
|
|
46
|
+
* const result = await hyperbeam.now({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
|
|
47
|
+
* console.log(result);
|
|
48
|
+
*/
|
|
49
|
+
async now({ path, json = false, }) {
|
|
50
|
+
return this.fetchHyperbeamPath({
|
|
51
|
+
path: `${this.url}/${this.processId}~process@1.0/now/${path}`,
|
|
52
|
+
json,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* calls the process device /compute function, which uses the currently evaluated state in the node
|
|
57
|
+
*
|
|
58
|
+
* @param path - The path to the compute resource
|
|
59
|
+
* @param json - Whether to return the result as JSON, defaults to true
|
|
60
|
+
* @returns The result of the compute operation
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
|
|
64
|
+
* const result = await hyperbeam.compute({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
|
|
65
|
+
* console.log(result);
|
|
66
|
+
*/
|
|
67
|
+
async compute({ path, json = false, }) {
|
|
68
|
+
return this.fetchHyperbeamPath({
|
|
69
|
+
path: `${this.url}/${this.processId}~process@1.0/compute/${path}`,
|
|
70
|
+
json,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Checks if the process is HyperBeam compatible and caches the result.
|
|
75
|
+
*
|
|
76
|
+
* @returns {Promise<boolean>} True if the process is HyperBeam compatible, false otherwise.
|
|
77
|
+
*/
|
|
78
|
+
async checkHyperBeamCompatibility({ minSlot, } = {}) {
|
|
79
|
+
// refetch if min slot is provided
|
|
80
|
+
if (minSlot !== undefined) {
|
|
81
|
+
this.isHyperBeamCompatible = undefined;
|
|
82
|
+
this.checkHyperBeamPromise = undefined;
|
|
83
|
+
}
|
|
84
|
+
if (this.checkHyperBeamPromise !== undefined) {
|
|
85
|
+
return this.checkHyperBeamPromise;
|
|
86
|
+
}
|
|
87
|
+
if (this.isHyperBeamCompatible !== undefined) {
|
|
88
|
+
return Promise.resolve(this.isHyperBeamCompatible);
|
|
89
|
+
}
|
|
90
|
+
const result = fetch(
|
|
91
|
+
// use /now to force a refresh of the cache state, then compute when calling it for keys
|
|
92
|
+
`${this.url.toString()}/${this.processId}~process@1.0/now`, {
|
|
93
|
+
method: 'HEAD',
|
|
94
|
+
signal: AbortSignal.timeout(this.hbTimeoutMs),
|
|
95
|
+
})
|
|
96
|
+
.then(async (res) => {
|
|
97
|
+
if (res.ok) {
|
|
98
|
+
if (minSlot !== undefined) {
|
|
99
|
+
const slotRes = await this.compute({
|
|
100
|
+
path: 'at-slot',
|
|
101
|
+
json: false,
|
|
102
|
+
});
|
|
103
|
+
const slot = Number(slotRes);
|
|
104
|
+
if (slot < minSlot) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
this.isHyperBeamCompatible = true;
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
this.isHyperBeamCompatible = false;
|
|
112
|
+
return false;
|
|
113
|
+
})
|
|
114
|
+
.catch((error) => {
|
|
115
|
+
this.logger.debug('Failed to check HyperBeam compatibility', {
|
|
116
|
+
cause: error,
|
|
117
|
+
});
|
|
118
|
+
this.isHyperBeamCompatible = false;
|
|
119
|
+
return false;
|
|
120
|
+
});
|
|
121
|
+
this.checkHyperBeamPromise = result;
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
async fetchHyperbeamPath({ path, json = true, }) {
|
|
125
|
+
try {
|
|
126
|
+
const url = new URL(path);
|
|
127
|
+
if (json) {
|
|
128
|
+
this.logger.debug('Fetching path as JSON', { path });
|
|
129
|
+
/**
|
|
130
|
+
* This is the (current) way to access data as json
|
|
131
|
+
* the old way is /~json@1.0/serialize path
|
|
132
|
+
*/
|
|
133
|
+
url.searchParams.set('require-codec', 'application/json');
|
|
134
|
+
url.searchParams.set('accept-bundle', 'true');
|
|
135
|
+
const res = await fetch(url);
|
|
136
|
+
if (!res.ok) {
|
|
137
|
+
throw new Error(`Failed to fetch path as JSON: ${res.statusText}`);
|
|
138
|
+
}
|
|
139
|
+
const jsonResult = await res
|
|
140
|
+
.json()
|
|
141
|
+
.then((json) => json)
|
|
142
|
+
.catch((error) => {
|
|
143
|
+
this.logger.error('Failed to parse JSON', {
|
|
144
|
+
cause: error,
|
|
145
|
+
});
|
|
146
|
+
throw new Error(`Received response but failed to parse JSON: ${error.message}`);
|
|
147
|
+
});
|
|
148
|
+
if (typeof jsonResult !== 'object' ||
|
|
149
|
+
jsonResult === null ||
|
|
150
|
+
!('body' in jsonResult)) {
|
|
151
|
+
throw new Error('Response body missing in JSON response');
|
|
152
|
+
}
|
|
153
|
+
return jsonResult.body;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
this.logger.debug('Fetching path as text', { path });
|
|
157
|
+
const res = await fetch(url);
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
throw new Error(`Failed to fetch path: ${res.statusText}`);
|
|
160
|
+
}
|
|
161
|
+
const body = await res.text();
|
|
162
|
+
return body;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
this.logger.error('Failed to fetch path as JSON', {
|
|
167
|
+
cause: error,
|
|
168
|
+
});
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.HB = HB;
|
package/lib/cjs/common/io.js
CHANGED
|
@@ -27,6 +27,7 @@ const arweave_js_2 = require("./arweave.js");
|
|
|
27
27
|
const ao_process_js_1 = require("./contracts/ao-process.js");
|
|
28
28
|
const error_js_1 = require("./error.js");
|
|
29
29
|
const faucet_js_1 = require("./faucet.js");
|
|
30
|
+
const hb_js_1 = require("./hyperbeam/hb.js");
|
|
30
31
|
const logger_js_1 = require("./logger.js");
|
|
31
32
|
const turbo_js_1 = require("./turbo.js");
|
|
32
33
|
class ARIO {
|
|
@@ -104,9 +105,9 @@ class ARIOReadable {
|
|
|
104
105
|
hyperbeamUrl;
|
|
105
106
|
paymentProvider; // TODO: this could be an array/map of payment providers
|
|
106
107
|
logger = logger_js_1.Logger.default;
|
|
108
|
+
hb;
|
|
107
109
|
constructor(config) {
|
|
108
110
|
this.arweave = config?.arweave ?? arweave_js_2.defaultArweave;
|
|
109
|
-
this.hyperbeamUrl = config?.hyperbeamUrl;
|
|
110
111
|
if (config === undefined || Object.keys(config).length === 0) {
|
|
111
112
|
this.process = new ao_process_js_1.AOProcess({
|
|
112
113
|
processId: constants_js_1.ARIO_MAINNET_PROCESS_ID,
|
|
@@ -123,6 +124,19 @@ class ARIOReadable {
|
|
|
123
124
|
else {
|
|
124
125
|
throw new error_js_1.InvalidContractConfigurationError();
|
|
125
126
|
}
|
|
127
|
+
// only use hyperbeam if the client has provided a hyperbeamUrl
|
|
128
|
+
// this will avoid overwhelming the HyperBeam node with requests
|
|
129
|
+
// as we shift using HyperBEAM for all ANT operations
|
|
130
|
+
if (config?.hyperbeamUrl !== undefined) {
|
|
131
|
+
this.hyperbeamUrl = config.hyperbeamUrl;
|
|
132
|
+
this.hb = new hb_js_1.HB({
|
|
133
|
+
url: this.hyperbeamUrl,
|
|
134
|
+
processId: this.process.processId,
|
|
135
|
+
});
|
|
136
|
+
this.logger.debug(`Using HyperBEAM node for process ${this.process.processId}`, {
|
|
137
|
+
hyperbeamUrl: this.hyperbeamUrl,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
126
140
|
this.paymentProvider = turbo_js_1.TurboArNSPaymentFactory.init({
|
|
127
141
|
paymentUrl: config?.paymentUrl,
|
|
128
142
|
});
|
|
@@ -222,6 +236,24 @@ class ARIOReadable {
|
|
|
222
236
|
});
|
|
223
237
|
}
|
|
224
238
|
async getBalance({ address }) {
|
|
239
|
+
if (this.hb && (await this.hb.checkHyperBeamCompatibility())) {
|
|
240
|
+
this.logger.debug('Getting balance from HyperBEAM', { address });
|
|
241
|
+
const res = await this.hb
|
|
242
|
+
.compute({
|
|
243
|
+
path: `balances/${address}`,
|
|
244
|
+
})
|
|
245
|
+
.then((res) => Number(res))
|
|
246
|
+
.catch((error) => {
|
|
247
|
+
this.logger.error('Failed to get balance from HyperBEAM', {
|
|
248
|
+
cause: error,
|
|
249
|
+
});
|
|
250
|
+
return null;
|
|
251
|
+
});
|
|
252
|
+
if (res !== null)
|
|
253
|
+
return res;
|
|
254
|
+
// else fall through to CU read
|
|
255
|
+
this.logger.info('Failed to get balance from HyperBEAM, failing over to to CU read', { address });
|
|
256
|
+
}
|
|
225
257
|
return this.process.read({
|
|
226
258
|
tags: [
|
|
227
259
|
{ name: 'Action', value: 'Balance' },
|
|
@@ -821,7 +853,7 @@ class ARIOWriteable extends ARIOReadable {
|
|
|
821
853
|
signer: this.signer,
|
|
822
854
|
});
|
|
823
855
|
}
|
|
824
|
-
async joinNetwork({ operatorStake, allowDelegatedStaking, allowedDelegates, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, port, properties, protocol, autoStake, observerAddress, }, options) {
|
|
856
|
+
async joinNetwork({ operatorStake, allowDelegatedStaking, allowedDelegates, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, port, properties, protocol, autoStake, observerAddress, services, }, options) {
|
|
825
857
|
const { tags = [] } = options || {};
|
|
826
858
|
const allTags = [
|
|
827
859
|
...tags,
|
|
@@ -878,6 +910,10 @@ class ARIOWriteable extends ARIOReadable {
|
|
|
878
910
|
name: 'Observer-Address',
|
|
879
911
|
value: observerAddress,
|
|
880
912
|
},
|
|
913
|
+
{
|
|
914
|
+
name: 'Services',
|
|
915
|
+
value: services ? JSON.stringify(services) : undefined,
|
|
916
|
+
},
|
|
881
917
|
];
|
|
882
918
|
return this.process.send({
|
|
883
919
|
signer: this.signer,
|
|
@@ -891,7 +927,7 @@ class ARIOWriteable extends ARIOReadable {
|
|
|
891
927
|
tags: [...tags, { name: 'Action', value: 'Leave-Network' }],
|
|
892
928
|
});
|
|
893
929
|
}
|
|
894
|
-
async updateGatewaySettings({ allowDelegatedStaking, allowedDelegates, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, port, properties, protocol, autoStake, observerAddress, }, options) {
|
|
930
|
+
async updateGatewaySettings({ allowDelegatedStaking, allowedDelegates, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, port, properties, protocol, autoStake, observerAddress, services, }, options) {
|
|
895
931
|
const { tags = [] } = options || {};
|
|
896
932
|
const allTags = [
|
|
897
933
|
...tags,
|
|
@@ -920,6 +956,10 @@ class ARIOWriteable extends ARIOReadable {
|
|
|
920
956
|
value: minDelegatedStake?.valueOf().toString(),
|
|
921
957
|
},
|
|
922
958
|
{ name: 'Auto-Stake', value: autoStake?.toString() },
|
|
959
|
+
{
|
|
960
|
+
name: 'Services',
|
|
961
|
+
value: services ? JSON.stringify(services) : undefined,
|
|
962
|
+
},
|
|
923
963
|
];
|
|
924
964
|
return this.process.send({
|
|
925
965
|
signer: this.signer,
|
package/lib/cjs/version.js
CHANGED
package/lib/esm/cli/cli.js
CHANGED
|
@@ -446,11 +446,7 @@ makeCommand({
|
|
|
446
446
|
makeCommand({
|
|
447
447
|
name: 'get-ants-for-address',
|
|
448
448
|
description: 'Get the list of ANTs owned by an address according to the ANT registry',
|
|
449
|
-
options: [
|
|
450
|
-
optionMap.address,
|
|
451
|
-
optionMap.antRegistryProcessId,
|
|
452
|
-
optionMap.hyperbeamUrl,
|
|
453
|
-
],
|
|
449
|
+
options: [optionMap.address, optionMap.antRegistryProcessId],
|
|
454
450
|
action: listAntsForAddress,
|
|
455
451
|
});
|
|
456
452
|
// # ANTS
|
package/lib/esm/cli/options.js
CHANGED
|
@@ -137,6 +137,10 @@ export const optionMap = {
|
|
|
137
137
|
description: 'The allowed delegates for the gateway. By default this is empty, meaning all are allowed delegate stake unless delegating is explicitly disallowed by the gateway',
|
|
138
138
|
type: 'array',
|
|
139
139
|
},
|
|
140
|
+
services: {
|
|
141
|
+
alias: '--services <services>',
|
|
142
|
+
description: 'JSON string of gateway services configuration (e.g., \'{"bundlers":[{"fqdn":"bundler.example.com","port":443,"protocol":"https","path":"/bundler"}]}\')',
|
|
143
|
+
},
|
|
140
144
|
skipConfirmation: {
|
|
141
145
|
alias: '--skip-confirmation',
|
|
142
146
|
description: 'Skip confirmation prompts',
|
|
@@ -332,6 +336,7 @@ export const globalOptions = [
|
|
|
332
336
|
optionMap.debug,
|
|
333
337
|
optionMap.arioProcessId,
|
|
334
338
|
optionMap.cuUrl,
|
|
339
|
+
optionMap.hyperbeamUrl,
|
|
335
340
|
];
|
|
336
341
|
export const writeActionOptions = [optionMap.skipConfirmation, optionMap.tags];
|
|
337
342
|
export const arnsPurchaseOptions = [
|
|
@@ -403,6 +408,7 @@ export const updateGatewaySettingsOptions = [
|
|
|
403
408
|
optionMap.fqdn,
|
|
404
409
|
optionMap.port,
|
|
405
410
|
optionMap.protocol,
|
|
411
|
+
optionMap.services,
|
|
406
412
|
];
|
|
407
413
|
export const joinNetworkOptions = [
|
|
408
414
|
...updateGatewaySettingsOptions,
|
package/lib/esm/cli/utils.js
CHANGED
|
@@ -127,6 +127,7 @@ function aoProcessFromOptions(options) {
|
|
|
127
127
|
export function readARIOFromOptions(options) {
|
|
128
128
|
setLoggerIfDebug(options);
|
|
129
129
|
return ARIO.init({
|
|
130
|
+
hyperbeamUrl: options.hyperbeamUrl,
|
|
130
131
|
process: aoProcessFromOptions({
|
|
131
132
|
cuUrl: 'https://cu.ardrive.io', // default to ardrive cu for ARIO process
|
|
132
133
|
...options,
|
|
@@ -173,6 +174,7 @@ export function writeARIOFromOptions(options) {
|
|
|
173
174
|
process: aoProcessFromOptions(options),
|
|
174
175
|
signer,
|
|
175
176
|
paymentUrl: options.paymentUrl,
|
|
177
|
+
hyperbeamUrl: options.hyperbeamUrl,
|
|
176
178
|
}),
|
|
177
179
|
signerAddress,
|
|
178
180
|
};
|
|
@@ -280,7 +282,44 @@ export function customTagsFromOptions(options) {
|
|
|
280
282
|
tags,
|
|
281
283
|
};
|
|
282
284
|
}
|
|
283
|
-
export function
|
|
285
|
+
export function servicesFromOptions(services) {
|
|
286
|
+
if (services === undefined || services === null || services === '') {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const parsed = JSON.parse(services);
|
|
291
|
+
// Validate structure
|
|
292
|
+
if (!parsed.bundlers || !Array.isArray(parsed.bundlers)) {
|
|
293
|
+
throw new Error('Services must have a "bundlers" array');
|
|
294
|
+
}
|
|
295
|
+
if (parsed.bundlers.length > 20) {
|
|
296
|
+
throw new Error('Maximum 20 bundlers allowed');
|
|
297
|
+
}
|
|
298
|
+
// Validate each bundler
|
|
299
|
+
for (const bundler of parsed.bundlers) {
|
|
300
|
+
if (!bundler.fqdn || typeof bundler.fqdn !== 'string') {
|
|
301
|
+
throw new Error('Each bundler must have a valid "fqdn" string');
|
|
302
|
+
}
|
|
303
|
+
if (typeof bundler.port !== 'number' ||
|
|
304
|
+
bundler.port < 0 ||
|
|
305
|
+
bundler.port > 65535) {
|
|
306
|
+
throw new Error('Each bundler must have a valid "port" (0-65535)');
|
|
307
|
+
}
|
|
308
|
+
if (bundler.protocol !== 'https') {
|
|
309
|
+
throw new Error('Each bundler protocol must be "https"');
|
|
310
|
+
}
|
|
311
|
+
if (!bundler.path || typeof bundler.path !== 'string') {
|
|
312
|
+
throw new Error('Each bundler must have a valid "path" string');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return parsed;
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
throw new Error(`Invalid services JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
export function gatewaySettingsFromOptions(options) {
|
|
322
|
+
const { allowDelegatedStaking, autoStake, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, observerAddress, port, properties, allowedDelegates, services, } = options;
|
|
284
323
|
return {
|
|
285
324
|
observerAddress,
|
|
286
325
|
allowDelegatedStaking,
|
|
@@ -295,6 +334,7 @@ export function gatewaySettingsFromOptions({ allowDelegatedStaking, autoStake, d
|
|
|
295
334
|
note,
|
|
296
335
|
port: port !== undefined ? +port : undefined,
|
|
297
336
|
properties,
|
|
337
|
+
services: servicesFromOptions(services),
|
|
298
338
|
};
|
|
299
339
|
}
|
|
300
340
|
export function requiredTargetAndQuantityFromOptions(options) {
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Logger } from '../logger.js';
|
|
2
|
+
export class HB {
|
|
3
|
+
url;
|
|
4
|
+
processId;
|
|
5
|
+
isHyperBeamCompatible;
|
|
6
|
+
checkHyperBeamPromise;
|
|
7
|
+
logger;
|
|
8
|
+
hbTimeoutMs;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.url = config.url;
|
|
11
|
+
this.processId = config.processId;
|
|
12
|
+
this.logger = config.logger ?? Logger.default;
|
|
13
|
+
this.hbTimeoutMs = config.hbTimeoutMs ?? 5000;
|
|
14
|
+
this.isHyperBeamCompatible = undefined;
|
|
15
|
+
this.checkHyperBeamPromise = this.checkHyperBeamCompatibility();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* fetches the meta data for the process
|
|
19
|
+
*
|
|
20
|
+
* @returns The meta data for the process
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
|
|
24
|
+
* const meta = await hyperbeam.meta();
|
|
25
|
+
* console.log(meta);
|
|
26
|
+
*/
|
|
27
|
+
async meta() {
|
|
28
|
+
const url = new URL(`${this.url}/${this.processId}~process@1.0/meta`);
|
|
29
|
+
return this.fetchHyperbeamPath({
|
|
30
|
+
path: url.toString(),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* calls the process device /now function, which evaluates the current process state pulling new messages
|
|
35
|
+
* to get the latest state
|
|
36
|
+
*
|
|
37
|
+
* @param path - The path to the hb state
|
|
38
|
+
* @param json - Whether to return the result as JSON, defaults to true
|
|
39
|
+
* @returns The result of the compute operation
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
|
|
43
|
+
* const result = await hyperbeam.now({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
|
|
44
|
+
* console.log(result);
|
|
45
|
+
*/
|
|
46
|
+
async now({ path, json = false, }) {
|
|
47
|
+
return this.fetchHyperbeamPath({
|
|
48
|
+
path: `${this.url}/${this.processId}~process@1.0/now/${path}`,
|
|
49
|
+
json,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* calls the process device /compute function, which uses the currently evaluated state in the node
|
|
54
|
+
*
|
|
55
|
+
* @param path - The path to the compute resource
|
|
56
|
+
* @param json - Whether to return the result as JSON, defaults to true
|
|
57
|
+
* @returns The result of the compute operation
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
|
|
61
|
+
* const result = await hyperbeam.compute({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
|
|
62
|
+
* console.log(result);
|
|
63
|
+
*/
|
|
64
|
+
async compute({ path, json = false, }) {
|
|
65
|
+
return this.fetchHyperbeamPath({
|
|
66
|
+
path: `${this.url}/${this.processId}~process@1.0/compute/${path}`,
|
|
67
|
+
json,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Checks if the process is HyperBeam compatible and caches the result.
|
|
72
|
+
*
|
|
73
|
+
* @returns {Promise<boolean>} True if the process is HyperBeam compatible, false otherwise.
|
|
74
|
+
*/
|
|
75
|
+
async checkHyperBeamCompatibility({ minSlot, } = {}) {
|
|
76
|
+
// refetch if min slot is provided
|
|
77
|
+
if (minSlot !== undefined) {
|
|
78
|
+
this.isHyperBeamCompatible = undefined;
|
|
79
|
+
this.checkHyperBeamPromise = undefined;
|
|
80
|
+
}
|
|
81
|
+
if (this.checkHyperBeamPromise !== undefined) {
|
|
82
|
+
return this.checkHyperBeamPromise;
|
|
83
|
+
}
|
|
84
|
+
if (this.isHyperBeamCompatible !== undefined) {
|
|
85
|
+
return Promise.resolve(this.isHyperBeamCompatible);
|
|
86
|
+
}
|
|
87
|
+
const result = fetch(
|
|
88
|
+
// use /now to force a refresh of the cache state, then compute when calling it for keys
|
|
89
|
+
`${this.url.toString()}/${this.processId}~process@1.0/now`, {
|
|
90
|
+
method: 'HEAD',
|
|
91
|
+
signal: AbortSignal.timeout(this.hbTimeoutMs),
|
|
92
|
+
})
|
|
93
|
+
.then(async (res) => {
|
|
94
|
+
if (res.ok) {
|
|
95
|
+
if (minSlot !== undefined) {
|
|
96
|
+
const slotRes = await this.compute({
|
|
97
|
+
path: 'at-slot',
|
|
98
|
+
json: false,
|
|
99
|
+
});
|
|
100
|
+
const slot = Number(slotRes);
|
|
101
|
+
if (slot < minSlot) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.isHyperBeamCompatible = true;
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
this.isHyperBeamCompatible = false;
|
|
109
|
+
return false;
|
|
110
|
+
})
|
|
111
|
+
.catch((error) => {
|
|
112
|
+
this.logger.debug('Failed to check HyperBeam compatibility', {
|
|
113
|
+
cause: error,
|
|
114
|
+
});
|
|
115
|
+
this.isHyperBeamCompatible = false;
|
|
116
|
+
return false;
|
|
117
|
+
});
|
|
118
|
+
this.checkHyperBeamPromise = result;
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
async fetchHyperbeamPath({ path, json = true, }) {
|
|
122
|
+
try {
|
|
123
|
+
const url = new URL(path);
|
|
124
|
+
if (json) {
|
|
125
|
+
this.logger.debug('Fetching path as JSON', { path });
|
|
126
|
+
/**
|
|
127
|
+
* This is the (current) way to access data as json
|
|
128
|
+
* the old way is /~json@1.0/serialize path
|
|
129
|
+
*/
|
|
130
|
+
url.searchParams.set('require-codec', 'application/json');
|
|
131
|
+
url.searchParams.set('accept-bundle', 'true');
|
|
132
|
+
const res = await fetch(url);
|
|
133
|
+
if (!res.ok) {
|
|
134
|
+
throw new Error(`Failed to fetch path as JSON: ${res.statusText}`);
|
|
135
|
+
}
|
|
136
|
+
const jsonResult = await res
|
|
137
|
+
.json()
|
|
138
|
+
.then((json) => json)
|
|
139
|
+
.catch((error) => {
|
|
140
|
+
this.logger.error('Failed to parse JSON', {
|
|
141
|
+
cause: error,
|
|
142
|
+
});
|
|
143
|
+
throw new Error(`Received response but failed to parse JSON: ${error.message}`);
|
|
144
|
+
});
|
|
145
|
+
if (typeof jsonResult !== 'object' ||
|
|
146
|
+
jsonResult === null ||
|
|
147
|
+
!('body' in jsonResult)) {
|
|
148
|
+
throw new Error('Response body missing in JSON response');
|
|
149
|
+
}
|
|
150
|
+
return jsonResult.body;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
this.logger.debug('Fetching path as text', { path });
|
|
154
|
+
const res = await fetch(url);
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
throw new Error(`Failed to fetch path: ${res.statusText}`);
|
|
157
|
+
}
|
|
158
|
+
const body = await res.text();
|
|
159
|
+
return body;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
this.logger.error('Failed to fetch path as JSON', {
|
|
164
|
+
cause: error,
|
|
165
|
+
});
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|