UNPKG

@ethersphere/bee-js

Version:
1,176 lines 63.5 kB
import { Binary, Objects, System, Types } from 'cafe-utility'; import { makeContentAddressedChunk } from "./chunk/cac.js"; import { downloadSingleOwnerChunk, makeSOCAddress, makeSingleOwnerChunk, uploadSingleOwnerChunkData } from "./chunk/soc.js"; import { makeFeedReader, makeFeedWriter } from "./feed/index.js"; import { areAllSequentialFeedsUpdateRetrievable } from "./feed/retrievable.js"; import * as bytes from "./modules/bytes.js"; import * as bzz from "./modules/bzz.js"; import * as chunk from "./modules/chunk.js"; import * as balance from "./modules/debug/balance.js"; import * as chequebook from "./modules/debug/chequebook.js"; import * as connectivity from "./modules/debug/connectivity.js"; import * as settlements from "./modules/debug/settlements.js"; import * as stake from "./modules/debug/stake.js"; import * as stamps from "./modules/debug/stamps.js"; import * as states from "./modules/debug/states.js"; import * as debugStatus from "./modules/debug/status.js"; import * as transactions from "./modules/debug/transactions.js"; import { postEnvelope } from "./modules/envelope.js"; import { createFeedManifest, fetchLatestFeedUpdate } from "./modules/feed.js"; import * as grantee from "./modules/grantee.js"; import * as gsoc from "./modules/gsoc.js"; import * as pinning from "./modules/pinning.js"; import * as pss from "./modules/pss.js"; import { rchash } from "./modules/rchash.js"; import * as status from "./modules/status.js"; import * as stewardship from "./modules/stewardship.js"; import * as tag from "./modules/tag.js"; import { CHUNK_SIZE, STAMPS_DEPTH_MAX, STAMPS_DEPTH_MIN } from "./types/index.js"; import { Bytes } from "./utils/bytes.js"; import { hashDirectory, streamDirectory, streamFiles } from "./utils/chunk-stream.js"; import { assertCollection, makeCollectionFromFileList } from "./utils/collection.js"; import { makeCollectionFromFS } from "./utils/collection.node.js"; import { prepareWebsocketData } from "./utils/data.js"; import { BeeArgumentError, BeeError } from "./utils/error.js"; import { fileArrayBuffer, isFile } from "./utils/file.js"; import { ResourceLocator } from "./utils/resource-locator.js"; import { getAmountForDuration, getDepthForSize, getStampCost } from "./utils/stamps.js"; import { BZZ, DAI } from "./utils/tokens.js"; import { asNumberString, assertData, assertFileData, makeTagUid, prepareAllTagsOptions, prepareBeeRequestOptions, prepareCollectionUploadOptions, prepareDownloadOptions, prepareFileUploadOptions, prepareGsocMessageHandler, preparePostageBatchOptions, preparePssMessageHandler, prepareRedundantUploadOptions, prepareTransactionOptions, prepareUploadOptions } from "./utils/type.js"; import { BatchId, EthAddress, Identifier, PeerAddress, PrivateKey, PublicKey, Reference, Span, Topic, TransactionId } from "./utils/typed-bytes.js"; import { assertBeeUrl, stripLastSlash } from "./utils/url.js"; /** * The main component that abstracts operations available on the main Bee API. * * Not all methods are always available as it depends in what mode is Bee node launched in. * For example gateway mode and light node mode has only limited set of endpoints enabled. */ export class Bee { /** * @param url URL on which is the main API of Bee node exposed * @param options */ constructor(url, options) { assertBeeUrl(url); // Remove last slash if present, as our endpoint strings starts with `/...` // which could lead to double slash in URL to which Bee responds with // unnecessary redirects. this.url = stripLastSlash(url); if (options?.signer) { this.signer = new PrivateKey(options.signer); } this.network = options?.network ?? 'gnosis'; this.requestOptions = { baseURL: this.url, timeout: options?.timeout ?? 0, headers: options?.headers, onRequest: options?.onRequest, httpAgent: options?.httpAgent, httpsAgent: options?.httpsAgent }; } /** * Upload data to a Bee node * * @param postageBatchId Postage BatchId to be used to upload the data with * @param data Data to be uploaded * @param options Additional options like tag, encryption, pinning, content-type and request options * * @returns reference is a content hash of the data * @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download) * @see [Bee API reference - `POST /bytes`](https://docs.ethswarm.org/api/#tag/Bytes/paths/~1bytes/post) */ async uploadData(postageBatchId, data, options, requestOptions) { postageBatchId = new BatchId(postageBatchId); assertData(data); if (options) { options = prepareRedundantUploadOptions(options); } return bytes.upload(this.getRequestOptionsForCall(requestOptions), data, postageBatchId, options); } /** * Requests content length for a `/bytes` reference * * @see [Bee API reference - `HEAD /bytes/`](https://docs.ethswarm.org/api/#tag/Bytes/paths/~1bytes~1%7Breference%7D/head) */ async probeData(reference, options) { reference = new Reference(reference); return bytes.head(this.getRequestOptionsForCall(options), reference); } /** * Download data as a byte array * * @param resource Swarm reference, Swarm CID, or ENS domain * @param options Options that affects the request behavior * @throws TypeError if some of the input parameters is not expected type * @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters * @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download) * @see [Bee API reference - `GET /bytes`](https://docs.ethswarm.org/api/#tag/Bytes/paths/~1bytes~1{reference}/get) */ async downloadData(resource, options, requestOptions) { if (options) { options = prepareDownloadOptions(options); } return bytes.download(this.getRequestOptionsForCall(requestOptions), new ResourceLocator(resource), options); } /** * Download data as a Readable stream * * @param resource Swarm reference, Swarm CID, or ENS domain * @param options Options that affects the request behavior * @throws TypeError if some of the input parameters is not expected type * @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters * @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download) * @see [Bee API reference - `GET /bytes`](https://docs.ethswarm.org/api/#tag/Bytes/paths/~1bytes~1{reference}/get) */ async downloadReadableData(resource, options, requestOptions) { if (options) { options = prepareDownloadOptions(options); } return bytes.downloadReadable(this.getRequestOptionsForCall(requestOptions), new ResourceLocator(resource), options); } /** * Upload chunk to a Bee node * * @param postageBatchId Postage BatchId to be used to upload the chunk with * @param data Raw chunk to be uploaded * @param options Additional options like tag, encryption, pinning, content-type and request options * * @returns reference is a content hash of the data * @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download) * @see [Bee API reference - `POST /chunks`](https://docs.ethswarm.org/api/#tag/Chunk/paths/~1chunks/post) */ async uploadChunk(stamp, data, options, requestOptions) { data = data instanceof Uint8Array ? data : data.data; if (options) { options = prepareUploadOptions(options); } if (data.length < Span.LENGTH) { throw new BeeArgumentError(`Chunk has to have size of at least ${Span.LENGTH}.`, data); } if (data.length > CHUNK_SIZE + Span.LENGTH) { throw new BeeArgumentError(`Chunk has to have size of at most ${CHUNK_SIZE + Span.LENGTH}.`, data); } return chunk.upload(this.getRequestOptionsForCall(requestOptions), data, stamp, options); } /** * Download chunk as a byte array * * @param reference Bee chunk reference in hex string (either 64 or 128 chars long) or ENS domain. * @param options Options that affects the request behavior * @throws TypeError if some of the input parameters is not expected type * @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters * @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download) * @see [Bee API reference - `GET /chunks`](https://docs.ethswarm.org/api/#tag/Chunk/paths/~1chunks~1{address}/get) */ async downloadChunk(reference, options, requestOptions) { reference = new Reference(reference); if (options) { options = prepareDownloadOptions(options); } return chunk.download(this.getRequestOptionsForCall(requestOptions), reference, options); } /** * Create a grantees list from the given array of public keys. * * The grantees list can be obtained with the `getGrantees` method. * * @param postageBatchId - The ID of the postage batch. * @param grantees - An array of public keys representing the grantees. * @param requestOptions - Optional request options. * @returns A promise that resolves to a `GranteesResult` object. */ async createGrantees(postageBatchId, grantees, requestOptions) { postageBatchId = new BatchId(postageBatchId); grantees = grantees.map(x => new PublicKey(x)); return grantee.createGrantees(this.getRequestOptionsForCall(requestOptions), postageBatchId, grantees); } /** * Retrieves the grantees for a given reference. * * @param reference - The reference. * @param requestOptions - Optional request options. * @returns A promise that resolves to a `GetGranteesResult` object. */ async getGrantees(reference, requestOptions) { reference = new Reference(reference); return grantee.getGrantees(reference, this.getRequestOptionsForCall(requestOptions)); } /** * Updates the grantees of a specific reference and history. * * @param reference - The reference. * @param history - The history. * @param postageBatchId - The ID of the postage batch. * @param grantees - The grantees. * @param requestOptions - Optional request options. * @returns A Promise that resolves to to a `GranteesResult` object. */ async patchGrantees(postageBatchId, reference, history, grantees, requestOptions) { postageBatchId = new BatchId(postageBatchId); reference = new Reference(reference); history = new Reference(history); const publicKeys = { add: grantees.add?.map(x => new PublicKey(x)) ?? [], revoke: grantees.revoke?.map(x => new PublicKey(x)) ?? [] }; return grantee.patchGrantees(postageBatchId, reference, history, publicKeys, this.getRequestOptionsForCall(requestOptions)); } /** * Upload single file to a Bee node. * * @param postageBatchId Postage BatchId to be used to upload the data with * @param data Data or file to be uploaded * @param name Optional name of the uploaded file * @param options Additional options like tag, encryption, pinning, content-type and request options * * @see [Bee docs - Keep your data alive / Postage stamps](https://docs.ethswarm.org/docs/develop/access-the-swarm/introduction/#keep-your-data-alive) * @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download) * @see [Bee API reference - `POST /bzz`](https://docs.ethswarm.org/api/#tag/BZZ/paths/~1bzz/post) * @returns reference is a content hash of the file */ async uploadFile(postageBatchId, data, name, options, requestOptions) { postageBatchId = new BatchId(postageBatchId); assertFileData(data); if (options) { options = prepareFileUploadOptions(options); } if (name && typeof name !== 'string') { throw new TypeError('name has to be string or undefined!'); } if (isFile(data)) { const fileData = await fileArrayBuffer(data); const fileName = name ?? data.name; const contentType = data.type; const fileOptions = { contentType, ...options }; return bzz.uploadFile(this.getRequestOptionsForCall(requestOptions), fileData, postageBatchId, fileName, fileOptions); } else { return bzz.uploadFile(this.getRequestOptionsForCall(requestOptions), data, postageBatchId, name, options); } } /** * Download single file. * * @param resource Swarm reference, Swarm CID, or ENS domain * @param path If reference points to manifest, then this parameter defines path to the file * @param options Options that affects the request behavior * @throws TypeError if some of the input parameters is not expected type * @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters * @see Data * @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download) * @see [Bee API reference - `GET /bzz`](https://docs.ethswarm.org/api/#tag/BZZ/paths/~1bzz~1%7Breference%7D~1%7Bpath%7D/get) */ async downloadFile(resource, path = '', options, requestOptions) { if (options) { options = prepareDownloadOptions(options); } return bzz.downloadFile(this.getRequestOptionsForCall(requestOptions), new ResourceLocator(resource), path, options); } /** * Download single file as a readable stream * * @param reference Bee file reference in hex string (either 64 or 128 chars long), ENS domain or Swarm CID. * @param path If reference points to manifest / collections, then this parameter defines path to the file * @param options Options that affects the request behavior * @throws TypeError if some of the input parameters is not expected type * @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters * * @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download) * @see [Bee API reference - `GET /bzz`](https://docs.ethswarm.org/api/#tag/BZZ/paths/~1bzz~1%7Breference%7D~1%7Bpath%7D/get) */ async downloadReadableFile(reference, path = '', options, requestOptions) { reference = new Reference(reference); if (options) { options = prepareDownloadOptions(options); } return bzz.downloadFileReadable(this.getRequestOptionsForCall(requestOptions), reference, path, options); } /** * Upload collection of files to a Bee node * * Uses the FileList API from the browser. * * The returned `UploadResult.tag` might be undefined if called in CORS-enabled environment. * This will be fixed upon next Bee release. https://github.com/ethersphere/bee-js/issues/406 * * @param postageBatchId Postage BatchId to be used to upload the data with * @param fileList list of files to be uploaded * @param options Additional options like tag, encryption, pinning and request options * * @see [Bee docs - Keep your data alive / Postage stamps](https://docs.ethswarm.org/docs/develop/access-the-swarm/introduction/#keep-your-data-alive) * @see [Bee docs - Upload directory](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download#upload-a-directory) * @see [Bee API reference - `POST /bzz`](https://docs.ethswarm.org/api/#tag/BZZ/paths/~1bzz/post) */ async uploadFiles(postageBatchId, fileList, options, requestOptions) { postageBatchId = new BatchId(postageBatchId); if (options) { options = prepareCollectionUploadOptions(options); } const data = makeCollectionFromFileList(fileList); return bzz.uploadCollection(this.getRequestOptionsForCall(requestOptions), data, postageBatchId, options); } async hashDirectory(dir) { return hashDirectory(dir); } async streamDirectory(postageBatchId, dir, onUploadProgress, options, requestOptions) { postageBatchId = new BatchId(postageBatchId); return streamDirectory(this, dir, postageBatchId, onUploadProgress, options, this.getRequestOptionsForCall(requestOptions)); } async streamFiles(postageBatchId, files, onUploadProgress, options, requestOptions) { postageBatchId = new BatchId(postageBatchId); return streamFiles(this, files, postageBatchId, onUploadProgress, options, this.getRequestOptionsForCall(requestOptions)); } /** * Upload Collection that you can assembly yourself. * * The returned `UploadResult.tag` might be undefined if called in CORS-enabled environment. * This will be fixed upon next Bee release. https://github.com/ethersphere/bee-js/issues/406 * * @param postageBatchId * @param collection * @param options Collections and request options */ async uploadCollection(postageBatchId, collection, options, requestOptions) { postageBatchId = new BatchId(postageBatchId); assertCollection(collection); if (options) { options = prepareCollectionUploadOptions(options); } return bzz.uploadCollection(this.getRequestOptionsForCall(requestOptions), collection, postageBatchId, options); } /** * Upload collection of files. * * Available only in Node.js as it uses the `fs` module. * * The returned `UploadResult.tag` might be undefined if called in CORS-enabled environment. * This will be fixed upon next Bee release. https://github.com/ethersphere/bee-js/issues/406 * * @param postageBatchId Postage BatchId to be used to upload the data with * @param dir the path of the files to be uploaded * @param options Additional options like tag, encryption, pinning and request options * * @see [Bee docs - Keep your data alive / Postage stamps](https://docs.ethswarm.org/docs/develop/access-the-swarm/introduction/#keep-your-data-alive) * @see [Bee docs - Upload directory](https://docs.ethswarm.org/docs/develop/access-the-swarm/upload-and-download#upload-a-directory) * @see [Bee API reference - `POST /bzz`](https://docs.ethswarm.org/api/#tag/BZZ/paths/~1bzz/post) */ async uploadFilesFromDirectory(postageBatchId, dir, options, requestOptions) { postageBatchId = new BatchId(postageBatchId); if (options) { options = prepareCollectionUploadOptions(options); } const data = await makeCollectionFromFS(dir); return bzz.uploadCollection(this.getRequestOptionsForCall(requestOptions), data, postageBatchId, options); } /** * Create a new Tag which is meant for tracking progres of syncing data across network. * * @param options Options that affects the request behavior * @see [Bee docs - Syncing / Tags](https://docs.ethswarm.org/docs/develop/access-the-swarm/syncing) * @see [Bee API reference - `POST /tags`](https://docs.ethswarm.org/api/#tag/Tag/paths/~1tags/post) */ async createTag(options) { return tag.createTag(this.getRequestOptionsForCall(options)); } /** * Fetches all tags. * * The listing is limited by options.limit. So you have to iterate using options.offset to get all tags. * * @param options Options that affects the request behavior * @throws TypeError if limit or offset are not numbers or undefined * @throws BeeArgumentError if limit or offset have invalid options * * @see [Bee docs - Syncing / Tags](https://docs.ethswarm.org/docs/develop/access-the-swarm/syncing) * @see [Bee API reference - `GET /tags`](https://docs.ethswarm.org/api/#tag/Tag/paths/~1tags/get) */ async getAllTags(options, requestOptions) { if (options) { options = prepareAllTagsOptions(options); } return tag.getAllTags(this.getRequestOptionsForCall(requestOptions), options?.offset, options?.limit); } /** * Retrieve tag information from Bee node * * @param tagUid UID or tag object to be retrieved * @param options Options that affects the request behavior * @throws TypeError if tagUid is in not correct format * * @see [Bee docs - Syncing / Tags](https://docs.ethswarm.org/docs/develop/access-the-swarm/syncing) * @see [Bee API reference - `GET /tags/{uid}`](https://docs.ethswarm.org/api/#tag/Tag/paths/~1tags~1{uid}/get) * */ async retrieveTag(tagUid, options) { tagUid = makeTagUid(tagUid); return tag.retrieveTag(this.getRequestOptionsForCall(options), tagUid); } /** * Delete Tag * * @param tagUid UID or tag object to be retrieved * @param options Options that affects the request behavior * @throws TypeError if tagUid is in not correct format * @throws BeeResponse error if something went wrong on the Bee node side while deleting the tag. * * @see [Bee docs - Syncing / Tags](https://docs.ethswarm.org/docs/develop/access-the-swarm/syncing) * @see [Bee API reference - `DELETE /tags/{uid}`](https://docs.ethswarm.org/api/#tag/Tag/paths/~1tags~1{uid}/delete) */ async deleteTag(tagUid, options) { tagUid = makeTagUid(tagUid); return tag.deleteTag(this.getRequestOptionsForCall(options), tagUid); } /** * Update tag's total chunks count. * * This is important if you are uploading individual chunks with a tag. Then upon finishing the final root chunk, * you can use this method to update the total chunks count for the tag. * * @param tagUid UID or tag object to be retrieved * @param reference The root reference that contains all the chunks to be counted * @param options Options that affects the request behavior * @throws TypeError if tagUid is in not correct format * @throws BeeResponse error if something went wrong on the Bee node side while deleting the tag. * * @see [Bee docs - Syncing / Tags](https://docs.ethswarm.org/docs/develop/access-the-swarm/syncing) * @see [Bee API reference - `PATCH /tags/{uid}`](https://docs.ethswarm.org/api/#tag/Tag/paths/~1tags~1{uid}/patch) */ async updateTag(tagUid, reference, options) { reference = new Reference(reference); tagUid = makeTagUid(tagUid); return tag.updateTag(this.getRequestOptionsForCall(options), tagUid, reference); } /** * Pin local data with given reference * * @param reference Data reference * @param options Options that affects the request behavior * @throws TypeError if reference is in not correct format * * @see [Bee docs - Pinning](https://docs.ethswarm.org/docs/develop/access-the-swarm/pinning) */ async pin(reference, options) { reference = new Reference(reference); return pinning.pin(this.getRequestOptionsForCall(options), reference); } /** * Unpin local data with given reference * * @param reference Data reference * @param options Options that affects the request behavior * @throws TypeError if reference is in not correct format * * @see [Bee docs - Pinning](https://docs.ethswarm.org/docs/develop/access-the-swarm/pinning) */ async unpin(reference, options) { reference = new Reference(reference); return pinning.unpin(this.getRequestOptionsForCall(options), reference); } /** * Get list of all locally pinned references * * @param options Options that affects the request behavior * @see [Bee docs - Pinning](https://docs.ethswarm.org/docs/develop/access-the-swarm/pinning) */ async getAllPins(options) { return pinning.getAllPins(this.getRequestOptionsForCall(options)); } /** * Get pinning status of chunk with given reference * * @param reference Bee data reference in hex string (either 64 or 128 chars long) or ENS domain. * @param options Options that affects the request behavior * @throws TypeError if some of the input parameters is not expected type * @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters * * @see [Bee docs - Pinning](https://docs.ethswarm.org/docs/develop/access-the-swarm/pinning) */ async getPin(reference, options) { reference = new Reference(reference); return pinning.getPin(this.getRequestOptionsForCall(options), reference); } /** * Instructs the Bee node to reupload a locally pinned data into the network. * * @param reference Bee data reference to be re-uploaded in hex string (either 64 or 128 chars long) or ENS domain. * @param options Options that affects the request behavior * @throws BeeArgumentError if the reference is not locally pinned * @throws TypeError if some of the input parameters is not expected type * @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters * * @see [Bee API reference - `PUT /stewardship`](https://docs.ethswarm.org/api/#tag/Stewardship/paths/~1stewardship~1{reference}/put) */ async reuploadPinnedData(postageBatchId, reference, options) { postageBatchId = new BatchId(postageBatchId); reference = new Reference(reference); await stewardship.reupload(this.getRequestOptionsForCall(options), postageBatchId, reference); } /** * Checks if content specified by reference is retrievable from the network. * * @param reference Bee data reference to be checked in hex string (either 64 or 128 chars long) or ENS domain. * @param options Options that affects the request behavior * @throws TypeError if some of the input parameters is not expected type * @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters * * @see [Bee API reference - `GET /stewardship`](https://docs.ethswarm.org/api/#tag/Stewardship/paths/~1stewardship~1{reference}/get) */ async isReferenceRetrievable(reference, options) { reference = new Reference(reference); return stewardship.isRetrievable(this.getRequestOptionsForCall(options), reference); } /** * Functions that validates if feed is retrievable in the network. * * If no index is passed then it check for "latest" update, which is a weaker guarantee as nobody can be really * sure what is the "latest" update. * * If index is passed then it validates all previous sequence index chunks if they are available as they are required * to correctly resolve the feed upto the given index update. * * @param type * @param owner * @param topic * @param index * @param options */ async isFeedRetrievable(owner, topic, index, options, requestOptions) { owner = new EthAddress(owner); topic = new Topic(topic); if (options) { options = prepareDownloadOptions(options); } if (!index) { try { await this.makeFeedReader(topic, owner, requestOptions).download(); return true; } catch (e) { const status = Objects.getDeep(e, 'status'); if (status === 404 || status === 500) { return false; } throw e; } } return areAllSequentialFeedsUpdateRetrievable(this, owner, topic, index, options, this.getRequestOptionsForCall(requestOptions)); } /** * Send data to recipient or target with Postal Service for Swarm. * * Because sending a PSS message is slow and CPU intensive, * it is not supposed to be used for general messaging but * most likely for setting up an encrypted communication * channel by sending an one-off message. * * **Warning! If the recipient Bee node is a light node, then he will never receive the message!** * This is because light nodes does not fully participate in the data exchange in Swarm network and hence the message won't arrive to them. * * @param postageBatchId Postage BatchId that will be assigned to sent message * @param topic Topic name * @param target Target message address prefix. Has a limit on length. Recommend to use `Utils.Pss.makeMaxTarget()` to get the most specific target that Bee node will accept. * @param data Message to be sent * @param recipient Recipient public key * @param options Options that affects the request behavior * @throws TypeError if `data`, `batchId`, `target` or `recipient` are in invalid format * * @see [Bee docs - PSS](https://docs.ethswarm.org/docs/develop/tools-and-features/pss) * @see [Bee API reference - `POST /pss`](https://docs.ethswarm.org/api/#tag/Postal-Service-for-Swarm/paths/~1pss~1send~1{topic}~1{targets}/post) */ async pssSend(postageBatchId, topic, target, data, recipient, options) { postageBatchId = new BatchId(postageBatchId); assertData(data); if (recipient) { recipient = new PublicKey(recipient); return pss.send(this.getRequestOptionsForCall(options), topic, target, data, postageBatchId, recipient); } else { return pss.send(this.getRequestOptionsForCall(options), topic, target, data, postageBatchId); } } /** * Subscribe to messages for given topic with Postal Service for Swarm * * **Warning! If connected Bee node is a light node, then he will never receive any message!** * This is because light nodes does not fully participate in the data exchange in Swarm network and hence the message won't arrive to them. * * @param topic Topic name * @param handler Message handler interface * * @returns Subscription to a given topic * * @see [Bee docs - PSS](https://docs.ethswarm.org/docs/develop/tools-and-features/pss) * @see [Bee API reference - `GET /pss`](https://docs.ethswarm.org/api/#tag/Postal-Service-for-Swarm/paths/~1pss~1subscribe~1{topic}/get) */ pssSubscribe(topic, handler) { handler = preparePssMessageHandler(handler); const ws = pss.subscribe(this.url, topic, this.requestOptions.headers); let cancelled = false; const cancel = () => { if (cancelled === false) { cancelled = true; // although the WebSocket API offers a `close` function, it seems that // with the library that we are using (isomorphic-ws) it doesn't close // the websocket properly, whereas `terminate` does if (ws.terminate) { ws.terminate(); } else { ws.close(); } // standard Websocket in browser does not have terminate function } }; const subscription = { topic, cancel }; ws.onmessage = async event => { const data = await prepareWebsocketData(event.data); if (data.length) { handler.onMessage(new Bytes(data), subscription); } }; ws.onerror = event => { // ignore errors after subscription was cancelled if (!cancelled) { handler.onError(new BeeError(event.message), subscription); } }; return subscription; } /** * Receive message with Postal Service for Swarm * * Because sending a PSS message is slow and CPU intensive, * it is not supposed to be used for general messaging but * most likely for setting up an encrypted communication * channel by sending an one-off message. * * This is a helper function to wait for exactly one message to * arrive and then cancel the subscription. Additionally a * timeout can be provided for the message to arrive or else * an error will be thrown. * * **Warning! If connected Bee node is a light node, then he will never receive any message!** * This is because light nodes does not fully participate in the data exchange in Swarm network and hence the message won't arrive to them. * * @param topic Topic name * @param timeoutMsec Timeout in milliseconds * * @returns Message in byte array * * @see [Bee docs - PSS](https://docs.ethswarm.org/docs/develop/tools-and-features/pss) * @see [Bee API reference - `GET /pss`](https://docs.ethswarm.org/api/#tag/Postal-Service-for-Swarm/paths/~1pss~1subscribe~1{topic}/get) */ async pssReceive(topic, timeoutMsec = 0) { if (typeof timeoutMsec !== 'number') { throw new TypeError('timeoutMsc parameter has to be a number!'); } return new Promise((resolve, reject) => { let timeout; const subscription = this.pssSubscribe(topic, { onError: error => { clearTimeout(timeout); subscription.cancel(); reject(error.message); }, onMessage: message => { clearTimeout(timeout); subscription.cancel(); resolve(message); } }); if (timeoutMsec > 0) { timeout = setTimeout(() => { subscription.cancel(); reject(new BeeError('pssReceive timeout')); }, timeoutMsec); } }); } gsocMine(targetOverlay, identifier, proximity = 12) { targetOverlay = new PeerAddress(targetOverlay); identifier = new Identifier(identifier); const start = 0xb33n; for (let i = 0n; i < 0xffffn; i++) { const signer = new PrivateKey(Binary.numberToUint256(start + i, 'BE')); const socAddress = makeSOCAddress(identifier, signer.publicKey().address()); // TODO: test the significance of the hardcoded 256 const actualProximity = 256 - Binary.proximity(socAddress.toUint8Array(), targetOverlay.toUint8Array()); if (actualProximity <= 256 - proximity) { return signer; } } throw Error('Could not mine a valid signer'); } async gsocSend(postageBatchId, signer, identifier, data, options, requestOptions) { postageBatchId = new BatchId(postageBatchId); signer = new PrivateKey(signer); identifier = new Identifier(identifier); const cac = makeContentAddressedChunk(data); const soc = makeSingleOwnerChunk(cac, identifier, signer); return gsoc.send(this.getRequestOptionsForCall(requestOptions), soc, postageBatchId, options); } gsocSubscribe(address, identifier, handler) { address = new EthAddress(address); identifier = new Identifier(identifier); handler = prepareGsocMessageHandler(handler); const socAddress = makeSOCAddress(identifier, address); const ws = gsoc.subscribe(this.url, socAddress, this.requestOptions.headers); let cancelled = false; const cancel = () => { if (cancelled === false) { cancelled = true; if (ws.terminate) { ws.terminate(); } else { ws.close(); } } }; const subscription = { address, cancel }; ws.onmessage = async event => { const data = await prepareWebsocketData(event.data); if (data.length) { handler.onMessage(new Bytes(data), subscription); } }; ws.onerror = event => { if (!cancelled) { handler.onError(new BeeError(event.message), subscription); } }; return subscription; } /** * Create feed manifest chunk and return the reference to it. * * Feed manifest chunk allows for a feed to be able to be resolved through `/bzz` endpoint. * * @param postageBatchId Postage BatchId to be used to create the Feed Manifest * @param topic Topic in hex or bytes * @param owner Owner's ethereum address in hex or bytes * @param options Options that affects the request behavior * * @see [Bee docs - Feeds](https://docs.ethswarm.org/docs/develop/tools-and-features/feeds) * @see [Bee API reference - `POST /feeds`](https://docs.ethswarm.org/api/#tag/Feed/paths/~1feeds~1{owner}~1{topic}/post) */ async createFeedManifest(postageBatchId, topic, owner, options, requestOptions) { postageBatchId = new BatchId(postageBatchId); topic = new Topic(topic); owner = new EthAddress(owner); if (options) { options = prepareUploadOptions(options); } return createFeedManifest(this.getRequestOptionsForCall(requestOptions), owner, topic, postageBatchId, options); } /** * Make a new feed reader for downloading feed updates. * * @param topic Topic in hex or bytes * @param owner Owner's ethereum address in hex or bytes * @param options Options that affects the request behavior * * @see [Bee docs - Feeds](https://docs.ethswarm.org/docs/develop/tools-and-features/feeds) */ makeFeedReader(topic, owner, options) { topic = new Topic(topic); owner = new EthAddress(owner); return makeFeedReader(this.getRequestOptionsForCall(options), topic, owner); } /** * Make a new feed writer for updating feeds * * @param topic Topic in hex or bytes * @param signer The signer's private key or a Signer instance that can sign data * @param options Options that affects the request behavior * * @see [Bee docs - Feeds](https://docs.ethswarm.org/docs/develop/tools-and-features/feeds) */ makeFeedWriter(topic, signer, options) { topic = new Topic(topic); signer = signer ? new PrivateKey(signer) : this.signer; if (!signer) { throw Error('No signer provided'); } return makeFeedWriter(this.getRequestOptionsForCall(options), topic, signer); } async fetchLatestFeedUpdate(topic, owner, requestOptions) { topic = new Topic(topic); owner = new EthAddress(owner); return fetchLatestFeedUpdate(this.getRequestOptionsForCall(requestOptions), owner, topic); } /** * Returns an object for reading single owner chunks * * @param ownerAddress The ethereum address of the owner * @param options Options that affects the request behavior * @see [Bee docs - Chunk Types](https://docs.ethswarm.org/docs/develop/tools-and-features/chunk-types#single-owner-chunks) */ makeSOCReader(ownerAddress, options) { ownerAddress = new EthAddress(ownerAddress); return { owner: ownerAddress, download: downloadSingleOwnerChunk.bind(null, this.getRequestOptionsForCall(options), ownerAddress) }; } /** * Returns an object for reading and writing single owner chunks * * @param signer The signer's private key or a Signer instance that can sign data * @param options Options that affects the request behavior * @see [Bee docs - Chunk Types](https://docs.ethswarm.org/docs/develop/tools-and-features/chunk-types#single-owner-chunks) */ makeSOCWriter(signer, options) { signer = signer ? new PrivateKey(signer) : this.signer; if (!signer) { throw Error('No signer provided'); } return { ...this.makeSOCReader(signer.publicKey().address(), options), upload: uploadSingleOwnerChunkData.bind(null, this.getRequestOptionsForCall(options), signer) }; } async createEnvelope(postageBatchId, reference, options) { postageBatchId = new BatchId(postageBatchId); reference = new Reference(reference); return postEnvelope(this.getRequestOptionsForCall(options), postageBatchId, reference); } /** * Get reserve commitment hash duration seconds */ async rchash(depth, anchor1, anchor2, options) { return rchash(this.getRequestOptionsForCall(options), depth, anchor1, anchor2); } /** * Ping the Bee node to see if there is a live Bee node on the given URL. * * @param options Options that affects the request behavior * @throws If connection was not successful throw error */ async checkConnection(options) { return status.checkConnection(this.getRequestOptionsForCall(options)); } /** * Ping the Bee node to see if there is a live Bee node on the given URL. * * @param options Options that affects the request behavior * @returns true if successful, false on error */ async isConnected(options) { try { await status.checkConnection(this.getRequestOptionsForCall(options)); } catch (e) { return false; } return true; } /** * Checks the `/gateway` endpoint to see if the remote API is a gateway. * * Do note that this is not a standard way to check for gateway nodes, * but some of the gateway tooling expose this endpoint. * * @param options */ async isGateway(options) { return status.isGateway(this.getRequestOptionsForCall(options)); } // Legacy debug API async getNodeAddresses(options) { return connectivity.getNodeAddresses(this.getRequestOptionsForCall(options)); } async getBlocklist(options) { return connectivity.getBlocklist(this.getRequestOptionsForCall(options)); } /** * Get list of peers for this node */ async getPeers(options) { return connectivity.getPeers(this.getRequestOptionsForCall(options)); } async removePeer(peer, options) { peer = new PeerAddress(peer); return connectivity.removePeer(this.getRequestOptionsForCall(options), peer); } async getTopology(options) { return connectivity.getTopology(this.getRequestOptionsForCall(options)); } async pingPeer(peer, options) { peer = new PeerAddress(peer); return connectivity.pingPeer(this.getRequestOptionsForCall(options), peer); } /* * Balance endpoints */ /** * Get the balances with all known peers including prepaid services */ async getAllBalances(options) { return balance.getAllBalances(this.getRequestOptionsForCall(options)); } /** * Get the balances with a specific peer including prepaid services * * @param address Swarm address of peer */ async getPeerBalance(address, options) { address = new PeerAddress(address); return balance.getPeerBalance(this.getRequestOptionsForCall(options), address); } /** * Get the past due consumption balances with all known peers */ async getPastDueConsumptionBalances(options) { return balance.getPastDueConsumptionBalances(this.getRequestOptionsForCall(options)); } /** * Get the past due consumption balance with a specific peer * * @param address Swarm address of peer */ async getPastDueConsumptionPeerBalance(address, options) { address = new PeerAddress(address); return balance.getPastDueConsumptionPeerBalance(this.getRequestOptionsForCall(options), address); } /* * Chequebook endpoints */ /** * Get the address of the chequebook contract used. * * **Warning:** The address is returned with 0x prefix unlike all other calls. * https://github.com/ethersphere/bee/issues/1443 */ async getChequebookAddress(options) { return chequebook.getChequebookAddress(this.getRequestOptionsForCall(options)); } /** * Get the balance of the chequebook */ async getChequebookBalance(options) { return chequebook.getChequebookBalance(this.getRequestOptionsForCall(options)); } /** * Get last cheques for all peers */ async getLastCheques(options) { return chequebook.getLastCheques(this.getRequestOptionsForCall(options)); } /** * Get last cheques for the peer * * @param address Swarm address of peer */ async getLastChequesForPeer(address, options) { address = new PeerAddress(address); return chequebook.getLastChequesForPeer(this.getRequestOptionsForCall(options), address); } /** * Get last cashout action for the peer * * @param address Swarm address of peer */ async getLastCashoutAction(address, options) { address = new PeerAddress(address); return chequebook.getLastCashoutAction(this.getRequestOptionsForCall(options), address); } /** * Cashout the last cheque for the peer * * @param address Swarm address of peer * @param options * @param options.gasPrice Gas price for the cashout transaction in WEI * @param options.gasLimit Gas limit for the cashout transaction in WEI */ async cashoutLastCheque(address, options, requestOptions) { address = new PeerAddress(address); if (options) { prepareTransactionOptions(options); } return chequebook.cashoutLastCheque(this.getRequestOptionsForCall(requestOptions), address, options); } /** * Deposit tokens from node wallet into chequebook * * @param amount Amount of tokens to deposit (must be positive integer) * @param gasPrice Gas Price in WEI for the transaction call * @return string Hash of the transaction * @deprecated Use `depositBZZToChequebook` instead. */ async depositTokens(amount, gasPrice, options) { return this.depositBZZToChequebook(amount, gasPrice, options); } /** * Deposit tokens from node wallet into chequebook * * @param amount Amount of tokens to deposit (must be positive integer) * @param gasPrice Gas Price in WEI for the transaction call * @return string Hash of the transaction */ async depositBZZToChequebook(amount, gasPrice, options) { const amountString = amount instanceof BZZ ? amount.toPLURString() : asNumberString(amount, { min: 1n, name: 'amount' }); let gasPriceString; if (gasPrice) { gasPriceString = asNumberString(amount, { min: 0n, name: 'gasPrice' }); } return chequebook.depositTokens(this.getRequestOptionsForCall(options), amountString, gasPriceString); } /** * Withdraw tokens from the chequebook to the node wallet * * @param amount Amount of tokens to withdraw (must be positive integer) * @param gasPrice Gas Price in WEI for the transaction call * @return string Hash of the transaction * @deprecated Use `withdrawBZZFromChequebook` instead. */ async withdrawTokens(amount, gasPrice, options) { return this.withdrawBZZFromChequebook(amount, gasPrice, options); } /** * Withdraw tokens from the chequebook to the node wallet * * @param amount Amount of tokens to withdraw (must be positive integer) * @param gasPrice Gas Price in WEI for the transaction call * @return string Hash of the transaction */ async withdrawBZZFromChequebook(amount, gasPrice, options) { // TODO: check BZZ in tests const amountString = amount instanceof BZZ ? amount.toPLURString() : asNumberString(amount, { min: 1n, name: 'amount' }); let gasPriceString; if (gasPrice) { gasPriceString = asNumberString(amount, { min: 0n, name: 'gasPrice' }); } return chequebook.withdrawTokens(this.getRequestOptionsForCall(options), amountString, gasPriceString); } async withdrawBZZToExternalWallet(amount, address, options) { amount = amount instanceof BZZ ? amount : BZZ.fromPLUR(amount); address = new EthAddress(address); return states.withdrawBZZ(this.getRequestOptionsForCall(options), amount, address); } async withdrawDAIToExternalWallet(amount, address, options) { amount = amount instanceof DAI ? amount : DAI.fromWei(amount); address = new EthAddress(address); return states.withdrawDAI(this.getRequestOptionsForCall(options), amount, address); } /* * Settlements endpoint */ /** * Get amount of sent and received from settlements with a peer * * @param address Swarm address of peer */ async getSettlements(address, options) { address = new PeerAddress(address); return settlements.getSettlements(this.getRequestOptionsForCall(options), address); } /** * Get settlements with all known peers and total amount sent or received */ async getAllSettlements(options) { return settlements.getAllSettlements(this.getRequestOptionsForCall(options)); } /** * Get status of node */ async getStatus(options) { return debugStatus.getDebugStatus(this.getRequestOptionsForCall(options)); } /** * Get health of node */ async getHealth(options) { return debugStatus.getHealth(this.getRequestOptionsForCall(options)); } /** * Get readiness of node */ async getReadiness(options) { return debugStatus.getReadiness(this.getRequestOptionsForCall(options)); } /** * Get mode information of node */ async getNodeInfo(options) { return debugStatus.getNodeInfo(this.getRequestOptionsForCall(options)); } /** * Connects to a node and checks if its version matches with the one that bee-js supports. * * @param options */ async isSupportedExactVersion(options) { return debugStatus.isSupportedExactVersion(this.getRequestOptionsForCall(options)); } /** * * Connects to a node and checks if its Main API version matches with the one that bee-js supports. * * This should be the main way how to check compatibility for your app and Bee node. * * @param options */ async isSupportedApiVersion(options) { return debugStatus.isSupportedApiVersion(this.getRequestOptionsForCall(options)); } /** * Returns object with all versions specified by the connected Bee node (properties prefixed with `bee*`) * and versions that bee-js supports (properties prefixed with `supported*`). * * @param options */ async getVersions(options) { return debugStatus.getVersions(this.getRequestOptionsForCall(options)); } /** * Get reserve state */ async getReserveState(options) { return states.getReserveState(this.getRequestOptionsForCall(options)); } /** * Get chain state */ async getChainState(options) { return states.getChainState(this.getRequestOptionsForCall(options)); } /** * Get wallet balances for DAI and BZZ of the Bee node * * @param options */ async getWalletBalance(options) { return states.getWalletBalance(this.getRequestOptionsForCall(options)); } /** * Creates new postage batch from the funds that the node has available in its Ethereum account. * * For better understanding what each parameter means and what are the optimal values please see * [Bee docs - Keep your data alive / Postage stamps](https://docs.ethswarm.org/docs/develop/access-the-swarm/introduction#keep-your-data-alive). * * **WARNING: THIS CREATES TRANSACTIONS THAT SPENDS MONEY** * * @param amount Amount that represents the value per chunk, has to be greater or equal zero. * @param depth Logarithm of the number of chunks that can be stamped with the batch. * @param options Options for creation of postage batch * @throws BeeArgumentError when negative amount or depth is specified * @throws TypeError if non-integer value is passed to amount or depth * * @see [Bee docs - Keep your data alive / Postage stamps](https://docs.ethswarm.org/docs/develop/access-the-swarm/introduction/#keep-your-data-alive) * @see [Bee Debug API reference - `POS