diff --git a/cli/docs/commands/README.md b/cli/docs/commands/README.md index 785278052..ea42f0d1f 100644 --- a/cli/docs/commands/README.md +++ b/cli/docs/commands/README.md @@ -57,6 +57,7 @@ * [`fluence provider init`](#fluence-provider-init) * [`fluence provider offer-create`](#fluence-provider-offer-create) * [`fluence provider offer-info`](#fluence-provider-offer-info) +* [`fluence provider offer-remove`](#fluence-provider-offer-remove) * [`fluence provider offer-update`](#fluence-provider-offer-update) * [`fluence provider register`](#fluence-provider-register) * [`fluence provider tokens-distribute`](#fluence-provider-tokens-distribute) @@ -1652,6 +1653,34 @@ ALIASES _See code: [src/commands/provider/offer-info.ts](https://github.com/fluencelabs/cli/blob/fluence-cli-v0.19.2/src/commands/provider/offer-info.ts)_ +## `fluence provider offer-remove` + +Remove offers + +``` +USAGE + $ fluence provider offer-remove [--no-input] [--offers ] [--env ] + [--priv-key ] + +FLAGS + --env= Fluence Environment to use when running the command + --no-input Don't interactively ask for any input from the user + --offers= Comma-separated list of offer names. To use all of your offers: --offers + all + --priv-key= !WARNING! for debug purposes only. Passing private keys through flags is + unsecure. On local env + 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 is + used by default when CLI is used in non-interactive mode + +DESCRIPTION + Remove offers + +ALIASES + $ fluence provider or +``` + +_See code: [src/commands/provider/offer-remove.ts](https://github.com/fluencelabs/cli/blob/fluence-cli-v0.19.2/src/commands/provider/offer-remove.ts)_ + ## `fluence provider offer-update` Update offers diff --git a/cli/src/commands/provider/offer-remove.ts b/cli/src/commands/provider/offer-remove.ts new file mode 100644 index 000000000..b8445075d --- /dev/null +++ b/cli/src/commands/provider/offer-remove.ts @@ -0,0 +1,36 @@ +/** + * Fluence CLI + * Copyright (C) 2024 Fluence DAO + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { BaseCommand, baseFlags } from "../../baseCommand.js"; +import { removeOffers } from "../../lib/chain/offer/updateOffers.js"; +import { CHAIN_FLAGS, OFFER_FLAG } from "../../lib/const.js"; +import { initCli } from "../../lib/lifeCycle.js"; + +export default class OfferRemove extends BaseCommand { + static override aliases = ["provider:or"]; + static override description = "Remove offers"; + static override flags = { + ...baseFlags, + ...OFFER_FLAG, + ...CHAIN_FLAGS, + }; + + async run(): Promise { + const { flags } = await initCli(this, await this.parse(OfferRemove)); + await removeOffers(flags); + } +} diff --git a/cli/src/lib/chain/offer/offer.ts b/cli/src/lib/chain/offer/offer.ts index 4d6b504db..110bae088 100644 --- a/cli/src/lib/chain/offer/offer.ts +++ b/cli/src/lib/chain/offer/offer.ts @@ -266,6 +266,11 @@ export async function createOffers(flags: OffersArgs) { ); } + if (offerIds.length === 0) { + commandObj.logToStderr("No offers created"); + return; + } + type GetOffersInfoReturnType = Awaited< ReturnType> >; @@ -633,6 +638,10 @@ export async function resolveCreatedOffers(flags: OfferArtifactsArgs) { export async function getOffersInfo( offers: T[], ): Promise<[T[], (T & { offerIndexerInfo: OfferDetail })[]]> { + if (offers.length === 0) { + return [[], []]; + } + const dealCliClient = await getDealCliClient(); const getOffersArg: Parameters[0] = { diff --git a/cli/src/lib/chain/offer/updateOffers.ts b/cli/src/lib/chain/offer/updateOffers.ts index 053c4753f..395cca728 100644 --- a/cli/src/lib/chain/offer/updateOffers.ts +++ b/cli/src/lib/chain/offer/updateOffers.ts @@ -18,8 +18,10 @@ import type { ComputeUnit } from "@fluencelabs/deal-ts-clients/dist/dealExplorerClient/types/schemes.js"; import { color } from "@oclif/color"; import chunk from "lodash-es/chunk.js"; +import omit from "lodash-es/omit.js"; import { commandObj } from "../../commandObj.js"; +import { initNewProviderArtifactsConfig } from "../../configs/project/providerArtifacts.js"; import { CLI_NAME, PROVIDER_ARTIFACTS_CONFIG_FULL_FILE_NAME, @@ -35,6 +37,7 @@ import { import { numToStr, uint8ArrayToHex } from "../../helpers/typesafeStringify.js"; import { splitErrorsAndResults } from "../../helpers/utils.js"; import { confirm } from "../../prompt.js"; +import { ensureFluenceEnv } from "../../resolveFluenceEnv.js"; import { cidStringToCIDV1Struct, peerIdHexStringToBase58String, @@ -103,6 +106,68 @@ export async function updateOffers(flags: OffersArgs) { ); } +export async function removeOffers(flags: OffersArgs) { + const offers = await resolveOffersFromProviderConfig(flags); + const offersFoundOnChain = await filterOffersFoundOnChain(offers); + const populatedTxs = await populateRemoveOffersTxs(offersFoundOnChain); + + const removeOffersTxs = [ + populatedTxs.flatMap(({ cuToRemoveTxs: txs }) => { + return txs.map(({ tx }) => { + return tx; + }); + }), + populatedTxs.flatMap(({ removePeersFromOffersTxs }) => { + return removePeersFromOffersTxs.map(({ tx }) => { + return tx; + }); + }), + populatedTxs.flatMap(({ removeOfferTx }) => { + return [removeOfferTx.tx]; + }), + ].flat(); + + if (removeOffersTxs.length === 0) { + commandObj.logToStderr("Nothing to remove for selected offers"); + return; + } + + printOffersToRemoveInfo(populatedTxs); + + if ( + !(await confirm({ + message: "Would you like to continue", + default: true, + })) + ) { + commandObj.logToStderr("Offers remove canceled"); + return; + } + + await signBatch( + `Removing offers:\n\n${populatedTxs + .map(({ offerName, offerId }) => { + return `${offerName} (${offerId})`; + }) + .join("\n")}`, + removeOffersTxs, + assertProviderIsRegistered, + ); + + const providerArtifactsConfig = await initNewProviderArtifactsConfig(); + + const fluenceEnv = await ensureFluenceEnv(); + + providerArtifactsConfig.offers[fluenceEnv] = omit( + providerArtifactsConfig.offers[fluenceEnv], + populatedTxs.map(({ offerName }) => { + return offerName; + }), + ); + + await providerArtifactsConfig.$commit(); +} + type OnChainOffer = Awaited< ReturnType >[number]; @@ -190,6 +255,44 @@ function populateUpdateOffersTxs(offersFoundOnChain: OnChainOffer[]) { ); } +function populateRemoveOffersTxs(offersFoundOnChain: OnChainOffer[]) { + return Promise.all( + offersFoundOnChain.map(async (offer) => { + const { offerName, offerId, offerIndexerInfo } = offer; + offer.computePeersFromProviderConfig = []; + + const peersOnChain = (await Promise.all( + offerIndexerInfo.peers.map(async ({ id, ...rest }) => { + return { + peerIdBase58: await peerIdHexStringToBase58String(id), + hexPeerId: id, + ...rest, + }; + }), + )) satisfies PeersOnChain; + + const removePeersFromOffersTxs = (await populatePeersToRemoveTxs( + offer, + peersOnChain, + )) satisfies Txs; + + const cuToRemoveTxs = ( + await populateCUToRemoveTxs(offer, peersOnChain) + ).flat() satisfies Txs; + + const removeOfferTx = await populateOfferRemoveTx(offer); + + return { + offerName, + offerId, + removePeersFromOffersTxs, + cuToRemoveTxs, + removeOfferTx, + }; + }), + ); +} + async function populatePeersToRemoveTxs( { computePeersFromProviderConfig }: OnChainOffer, peersOnChain: PeersOnChain, @@ -374,6 +477,15 @@ async function populateCUToRemoveTxs( }); } +async function populateOfferRemoveTx({ offerId }: OnChainOffer) { + const { dealClient } = await getDealClient(); + const market = dealClient.getMarket(); + return { + description: `\nRemoving offer: ${offerId}`, + tx: populateTx(market.removeOffer, offerId), + }; +} + async function populateCUToAddTxs( { computePeersFromProviderConfig }: OnChainOffer, peersOnChain: PeersOnChain, @@ -484,3 +596,44 @@ function printOffersToUpdateInfo( .join("\n\n")}`, ); } + +function printOffersToRemoveInfo( + populatedTxs: Awaited>, +) { + commandObj.logToStderr( + `Offers to remove:\n\n${populatedTxs + .flatMap( + ({ + offerId, + offerName, + removePeersFromOffersTxs, + cuToRemoveTxs, + removeOfferTx, + }) => { + const allTxs = [ + ...removePeersFromOffersTxs, + ...cuToRemoveTxs, + removeOfferTx, + ]; + + if (allTxs.length === 0) { + return []; + } + + return [ + `Offer ${color.green(offerName)} with id ${color.yellow( + offerId, + )}:\n${allTxs + .filter((tx): tx is typeof tx & { description: string } => { + return "description" in tx; + }) + .map(({ description }) => { + return description; + }) + .join("\n")}\n`, + ]; + }, + ) + .join("\n\n")}`, + ); +}