diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index bc18e1afb..34fc1c947 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -73,10 +73,11 @@ if (BILLING_ENABLED === "true") { const { AuthInterceptor } = require("./auth/services/auth.interceptor"); appHono.use(container.resolve(AuthInterceptor).intercept()); // eslint-disable-next-line @typescript-eslint/no-var-requires - const { startTrialRouter, getWalletListRouter, signAndBroadcastTxRouter, checkoutRouter, stripeWebhook } = require("./billing"); + const { startTrialRouter, getWalletListRouter, signAndBroadcastTxRouter, checkoutRouter, stripeWebhook, trialBidsRouter } = require("./billing"); appHono.route("/", startTrialRouter); appHono.route("/", getWalletListRouter); appHono.route("/", signAndBroadcastTxRouter); + appHono.route("/", trialBidsRouter); appHono.route("/", checkoutRouter); appHono.route("/", stripeWebhook); // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/apps/api/src/billing/controllers/wallet/wallet.controller.ts b/apps/api/src/billing/controllers/wallet/wallet.controller.ts index d4b1864b2..852ed0c51 100644 --- a/apps/api/src/billing/controllers/wallet/wallet.controller.ts +++ b/apps/api/src/billing/controllers/wallet/wallet.controller.ts @@ -5,8 +5,10 @@ import { Protected } from "@src/auth/services/auth.service"; import type { WalletListOutputResponse, WalletOutputResponse } from "@src/billing/http-schemas/wallet.schema"; import type { SignTxRequestInput, SignTxResponseOutput, StartTrialRequestInput } from "@src/billing/routes"; import { GetWalletQuery } from "@src/billing/routes/get-wallet-list/get-wallet-list.router"; +import { GetTrialBidListRequestInput } from "@src/billing/routes/trial-bids/trial-bids.router"; import { WalletInitializerService } from "@src/billing/services"; import { RefillService } from "@src/billing/services/refill/refill.service"; +import { TrialValidationService } from "@src/billing/services/trial-validation/trial-validation.service"; import { TxSignerService } from "@src/billing/services/tx-signer/tx-signer.service"; import { GetWalletOptions, WalletReaderService } from "@src/billing/services/wallet-reader/wallet-reader.service"; import { WithTransaction } from "@src/core"; @@ -17,6 +19,7 @@ export class WalletController { private readonly walletInitializer: WalletInitializerService, private readonly signerService: TxSignerService, private readonly refillService: RefillService, + private readonly trialValidationService: TrialValidationService, private readonly walletReaderService: WalletReaderService ) {} @@ -45,4 +48,8 @@ export class WalletController { async refillWallets() { await this.refillService.refillAllFees(); } + + async getTrialBidList({ address, dseq }: GetTrialBidListRequestInput) { + return this.trialValidationService.getTrialBidList(address, dseq); + } } diff --git a/apps/api/src/billing/http-schemas/bids.schema.ts b/apps/api/src/billing/http-schemas/bids.schema.ts new file mode 100644 index 000000000..681d98aad --- /dev/null +++ b/apps/api/src/billing/http-schemas/bids.schema.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; + +// TODO: Extract into a types package +const BidOutputSchema = z.object({ + bid_id: z.object({ + owner: z.string().openapi({}), + dseq: z.string().openapi({}), + gseq: z.number().openapi({}), + oseq: z.number().openapi({}), + provider: z.string().openapi({}) + }), + state: z.string().openapi({}), + price: z.object({ + denom: z.string().openapi({}), + amount: z.string().openapi({}) + }), + created_at: z.string().openapi({}), + resources_offer: z.array( + z.object({ + resources: z.object({ + id: z.number().openapi({}) + }) + }) + ) +}); + +export const BidListResponseOutputSchema = z.object({ + bids: z.array(BidOutputSchema) +}); +export type BidListOutputResponse = z.infer; diff --git a/apps/api/src/billing/routes/index.ts b/apps/api/src/billing/routes/index.ts index 428aa73e4..2ef67c0c2 100644 --- a/apps/api/src/billing/routes/index.ts +++ b/apps/api/src/billing/routes/index.ts @@ -3,3 +3,4 @@ export * from "@src/billing/routes/get-wallet-list/get-wallet-list.router"; export * from "@src/billing/routes/checkout/checkout.router"; export * from "@src/billing/routes/sign-and-broadcast-tx/sign-and-broadcast-tx.router"; export * from "@src/billing/routes/stripe-webhook/stripe-webhook.router"; +export * from "@src/billing/routes/trial-bids/trial-bids.router"; diff --git a/apps/api/src/billing/routes/trial-bids/trial-bids.router.ts b/apps/api/src/billing/routes/trial-bids/trial-bids.router.ts new file mode 100644 index 000000000..793d30b2c --- /dev/null +++ b/apps/api/src/billing/routes/trial-bids/trial-bids.router.ts @@ -0,0 +1,38 @@ +import { createRoute } from "@hono/zod-openapi"; +import { container } from "tsyringe"; +import { z } from "zod"; + +import { WalletController } from "@src/billing/controllers/wallet/wallet.controller"; +import { BidListResponseOutputSchema } from "@src/billing/http-schemas/bids.schema"; +import { OpenApiHonoHandled } from "@src/core/services/open-api-hono-handled/open-api-hono-handled"; + +export const GetTrialBidListRequestInputSchema = z.object({ + address: z.string().openapi({}), + dseq: z.string().openapi({}) +}); +export type GetTrialBidListRequestInput = z.infer; + +const route = createRoute({ + method: "get", + path: "/v1/trial-bids", + summary: "Get a list of bids for trial accounts", + tags: ["Bids"], + request: { + query: GetTrialBidListRequestInputSchema + }, + responses: { + 200: { + description: "Returns a list of bids for trial accounts", + content: { + "application/json": { + schema: BidListResponseOutputSchema + } + } + } + } +}); +export const trialBidsRouter = new OpenApiHonoHandled(); + +trialBidsRouter.openapi(route, async function routeGetTrialBidList(c) { + return c.json(await container.resolve(WalletController).getTrialBidList(c.req.valid("query")), 200); +}); diff --git a/apps/api/src/billing/services/anonymous-validate/anonymous-validate.ts b/apps/api/src/billing/services/trial-validation/trial-validation.service.ts similarity index 66% rename from apps/api/src/billing/services/anonymous-validate/anonymous-validate.ts rename to apps/api/src/billing/services/trial-validation/trial-validation.service.ts index ee7af364b..91a79f77c 100644 --- a/apps/api/src/billing/services/anonymous-validate/anonymous-validate.ts +++ b/apps/api/src/billing/services/trial-validation/trial-validation.service.ts @@ -1,13 +1,15 @@ import * as v1beta4 from "@akashnetwork/akash-api/v1beta4"; import { EncodeObject } from "@cosmjs/proto-signing"; +import axios from "axios"; import { singleton } from "tsyringe"; import { UserWalletOutput } from "@src/billing/repositories"; +import { apiNodeUrl, betaTypeVersionMarket } from "@src/utils/constants"; import { ChainErrorService } from "../chain-error/chain-error.service"; @singleton() -export class AnonymousValidateService { - private readonly authorizedProviders = [ +export class TrialValidationService { + readonly authorizedProviders = [ "akash1824w2vqx57n8zr8707dnyh85kjrkfkrrs94pk9", "akash19ah5c95kq4kz2g6q5rdkdgt80kc3xycsd8plq8", "akash1g7az2pus6atgeufgttlcnl0wzlzwd0lrsy6d7s", @@ -33,4 +35,19 @@ export class AnonymousValidateService { return true; } + + async getTrialBidList(address: string, dseq: string): Promise<{ bids: any[] }> { + const response = await axios.get(`${apiNodeUrl}/akash/market/${betaTypeVersionMarket}/bids/list`, { + params: { + "filters.owner": address, + "filters.dseq": dseq + } + }); + + if (response.data.bids.length === 0) { + return { bids: [] }; + } + + return { bids: response.data.bids.filter((bid: { bid: { bid_id: { provider: string } } }) => this.authorizedProviders.includes(bid.bid.bid_id.provider)) }; + } } diff --git a/apps/api/src/billing/services/tx-signer/tx-signer.service.ts b/apps/api/src/billing/services/tx-signer/tx-signer.service.ts index 02699ba49..de2202bbd 100644 --- a/apps/api/src/billing/services/tx-signer/tx-signer.service.ts +++ b/apps/api/src/billing/services/tx-signer/tx-signer.service.ts @@ -12,8 +12,8 @@ import { InjectTypeRegistry } from "@src/billing/providers/type-registry.provide import { UserWalletOutput, UserWalletRepository } from "@src/billing/repositories"; import { MasterWalletService } from "@src/billing/services"; import { BalancesService } from "@src/billing/services/balances/balances.service"; -import { AnonymousValidateService } from "../anonymous-validate/anonymous-validate"; import { ChainErrorService } from "../chain-error/chain-error.service"; +import { TrialValidationService } from "../trial-validation/trial-validation.service"; type StringifiedEncodeObject = Omit & { value: string }; type SimpleSigningStargateClient = { @@ -34,7 +34,7 @@ export class TxSignerService { private readonly balancesService: BalancesService, private readonly authService: AuthService, private readonly chainErrorService: ChainErrorService, - private readonly anonymousValidateService: AnonymousValidateService + private readonly anonymousValidateService: TrialValidationService ) {} async signAndBroadcast(userId: UserWalletOutput["userId"], messages: StringifiedEncodeObject[]) { diff --git a/apps/deploy-web/src/components/new-deployment/CreateLease.tsx b/apps/deploy-web/src/components/new-deployment/CreateLease.tsx index 0bab8de19..0583cc194 100644 --- a/apps/deploy-web/src/components/new-deployment/CreateLease.tsx +++ b/apps/deploy-web/src/components/new-deployment/CreateLease.tsx @@ -62,7 +62,7 @@ export const CreateLease: React.FunctionComponent = ({ dseq }) => { const [selectedBids, setSelectedBids] = useState<{ [gseq: string]: BidDto }>({}); const [filteredBids, setFilteredBids] = useState>([]); const [search, setSearch] = useState(""); - const { address, signAndBroadcastTx } = useWallet(); + const { address, signAndBroadcastTx, isTrialing, isManaged } = useWallet(); const { localCert } = useCertificate(); const router = useRouter(); const [numberOfRequests, setNumberOfRequests] = useState(0); @@ -70,7 +70,7 @@ export const CreateLease: React.FunctionComponent = ({ dseq }) => { const warningRequestsReached = numberOfRequests > WARNING_NUM_OF_BID_REQUESTS; const maxRequestsReached = numberOfRequests > MAX_NUM_OF_BID_REQUESTS; const { favoriteProviders } = useLocalNotes(); - const { data: bids, isLoading: isLoadingBids } = useBidList(address, dseq, { + const { data: bids, isLoading: isLoadingBids } = useBidList(isTrialing, address, dseq, { initialData: [], refetchInterval: REFRESH_BIDS_INTERVAL, onSuccess: () => { @@ -92,7 +92,6 @@ export const CreateLease: React.FunctionComponent = ({ dseq }) => { const allClosed = (bids?.length || 0) > 0 && bids?.every(bid => bid.state === "closed"); const { enqueueSnackbar, closeSnackbar } = useSnackbar(); - const wallet = useWallet(); const { closeDeploymentConfirm } = useManagedDeploymentConfirm(); useEffect(() => { @@ -118,7 +117,7 @@ export const CreateLease: React.FunctionComponent = ({ dseq }) => { } const sendManifestNotification = - !wallet.isManaged && + !isManaged && enqueueSnackbar(, { variant: "info", autoHideDuration: null @@ -148,7 +147,7 @@ export const CreateLease: React.FunctionComponent = ({ dseq }) => { setIsSendingManifest(false); } - }, [selectedBids, dseq, providers, localCert, wallet.isManaged, enqueueSnackbar, closeSnackbar, router]); + }, [selectedBids, dseq, providers, localCert, isManaged, enqueueSnackbar, closeSnackbar, router]); // Filter bids useEffect(() => { diff --git a/apps/deploy-web/src/queries/useBidQuery.ts b/apps/deploy-web/src/queries/useBidQuery.ts index 2b443a4b6..d98195552 100644 --- a/apps/deploy-web/src/queries/useBidQuery.ts +++ b/apps/deploy-web/src/queries/useBidQuery.ts @@ -1,4 +1,4 @@ -import { useQuery } from "react-query"; +import { QueryKey, useQuery, UseQueryOptions } from "react-query"; import axios from "axios"; import { BidDto, RpcBid } from "@src/types/deployment"; @@ -6,10 +6,10 @@ import { ApiUrlService } from "@src/utils/apiUtils"; import { useSettings } from "../context/SettingsProvider"; import { QueryKeys } from "./queryKeys"; -async function getBidList(apiEndpoint: string, address: string, dseq: string): Promise | null> { +async function getBidList(apiEndpoint: string, isTrialing: boolean, address: string, dseq: string): Promise | null> { if (!address || !dseq) return null; - const response = await axios.get(ApiUrlService.bidList(apiEndpoint, address, dseq)); + const response = await axios.get(isTrialing ? ApiUrlService.trialBidList(address, dseq) : ApiUrlService.bidList(apiEndpoint, address, dseq)); return response.data.bids.map((b: RpcBid) => ({ id: b.bid.bid_id.provider + b.bid.bid_id.dseq + b.bid.bid_id.gseq + b.bid.bid_id.oseq, @@ -24,9 +24,14 @@ async function getBidList(apiEndpoint: string, address: string, dseq: string): P })); } -export function useBidList(address: string, dseq: string, options) { +export function useBidList( + isTrialing: boolean, + address: string, + dseq: string, + options?: Omit, "queryKey" | "queryFn"> +) { const { settings } = useSettings(); - return useQuery(QueryKeys.getBidListKey(address, dseq), () => getBidList(settings.apiEndpoint, address, dseq), options); + return useQuery(QueryKeys.getBidListKey(address, dseq), () => getBidList(settings.apiEndpoint, isTrialing, address, dseq), options); } async function getBidInfo(apiEndpoint: string, address: string, dseq: string, gseq: number, oseq: number, provider: string): Promise { diff --git a/apps/deploy-web/src/utils/apiUtils.ts b/apps/deploy-web/src/utils/apiUtils.ts index 5dd81ab58..318ef3500 100644 --- a/apps/deploy-web/src/utils/apiUtils.ts +++ b/apps/deploy-web/src/utils/apiUtils.ts @@ -20,6 +20,9 @@ export class ApiUrlService { static bidList(apiEndpoint: string, address: string, dseq: string) { return `${apiEndpoint}/akash/market/${networkStore.marketApiVersion}/bids/list?filters.owner=${address}&filters.dseq=${dseq}`; } + static trialBidList(address: string, dseq: string) { + return `${this.baseApiUrl}/v1/trial-bids?address=${address}&dseq=${dseq}`; + } static bidInfo(apiEndpoint: string, address: string, dseq: string, gseq: number, oseq: number, provider: string) { return `${apiEndpoint}/akash/market/${networkStore.marketApiVersion}/bids/info?id.owner=${address}&id.dseq=${dseq}&id.gseq=${gseq}&id.oseq=${oseq}&id.provider=${provider}`; }